Skip to content

Curry: proposed new functional programming, er, function.

7 messages · Hadley Wickham, Gabor Grothendieck, Yike Lu

#
Hadley Wickham-2 wrote
I've been playing around with this for a while. One flaw I found - it
doesn't handle nested Curries very well (the naive implementation in
roxygen/functional does).

e.g.:

foo=function(x,y,z) x+y+z
Curry(Curry("foo",3),4)(3)
# 10

Curry(Curry(foo,3),4)(3)
# hangs

foo4=function(a,b,c,d)
Curry(Curry(Curry("foo4",3),4),1)(3)
# hangs

I was also curious if there was some trick to force a function eval when the
list of arguments got exhausted (for example, a triple Curry on foo above
would leave no arguments so would trigger eval into 10).

--
View this message in context: http://r.789695.n4.nabble.com/Curry-proposed-new-functional-programming-er-function-tp917654p4631127.html
Sent from the R devel mailing list archive at Nabble.com.
1 day later
#
That's easily fixed:

Curry <- function(FUN, ...) {
  args <- match.call(expand.dots = FALSE)$...
  args$... <- as.name("...")

  env <- parent.frame()

  if (is.name(FUN)) {
    fname <- FUN
  } else if (is.character(FUN)) {
    fname <- as.name(FUN)
  } else if (is.function(FUN)){
    fname <- FUN
    # env$FUN <- FUN
  } else {
    stop("FUN not function or name of function")
  }
  curry_call <- as.call(c(list(fname), args))

  f <- eval(call("function", as.pairlist(alist(... = )), curry_call))
  environment(f) <- env
  f
}


Curry(Curry(foo,3),4)
I don't think that would be a good idea - there's a big difference
between a function with no arguments and the result of calling that
function.

Hadley
#
So here's the way I'm reading this:

Original:
curry_call is the function body you're constructing, which is itself 
just a one liner which calls the symbol FUN with the appropriate 
substitutions.

call("function", [...]) calls the "function" function, which itself 
takes 2 arguments: the list of formal args and the function body.
eval of this call returns the newly constructed function, which you 
assign to f. Then you assign the parent.frame() as the environment of f, 
except with the symbol FUN assigned as the original argument FUN.

However, upon looking at the debugger, I find that env$FUN<-FUN assigns 
FUN in Global Scope if Curry is called from the top level.
A nested Curry call then creates FUN=function(...) FUN([...]), a 
recursive infinite loop.

New:
The recursion is obviously removed now, but what's the new version do?

As far as I can tell, it returns  a structure like...

function(...){function(...) {original_function_body(curried_arg, ...=...)}}

Comparing and contrasting to the version in "functional" package:
1) quotes work (can do Curry(quote(foo), 2) where the "functional" 
version can't)
2) environment capture works in both constructions
3) Your new version is exceptionally transparent, as the function body 
gets stored so that when you print the body later, you can see the original

As far as 0 argument functions, I understand the difference, that idea 
came from a programming language (q/kdb+) I know that supports a neat 
compact syntax for this:

Suppose in R the function was f(x,y,z) x + y + z

In q, one could do:
f[1;2] // returns the curried form
f[1;2] each (1 2 3 4 5) // equivalent to Map(function(z) f(1,2,z), 1:5) 
or Map(Curry(f, 1, 2), 1:5)
f[1;2;3] // returns 6
f[1;2][3] // returns 6


Probably a slightly different but related concept - elided arguments, 
just a curry with automatic eval when all argument slots are filled.

Thanks, this has been very enlightening.

Yike
On 5/25/2012 9:49 AM, Hadley Wickham wrote:
#
On Fri, May 25, 2012 at 3:14 PM, Yike Lu <yikelu.home at gmail.com> wrote:
Yup.  With a bit more infrastructure you could probably modify it so
that multiple curries collapsed into the equivalent single curry.
Yes, that was a really bad idea - not sure why I didn't see the
problems when I first wrote it.
I can see why that's useful at the language level, but I think it
would be confusing to do so in R.

Hadley
#
On 5/25/12 5:23 PM, Hadley Wickham wrote:
Yes I could see how one would do that - if the match.call detects a 
Curry as the first function being called, we would short circuit the 
usual evaluation into a different path which properly collapses all the 
nesting.

It's interesting how R offers these facilities to override the usual 
evaluation order, but if one does that too much it could easily become 
confusing. I was looking at Rpipe the other day 
(https://github.com/slycoder/Rpipe) and the way he implements it is by 
defining his own Eval.

Cheers,
#
On Sat, May 26, 2012 at 12:30 PM, Yike Lu <yikelu.home at gmail.com> wrote:
The proto package does currying on proto methods by defining $.proto
appropriately:

library(proto)
p <- proto(a = 1, b = 2)

# same as ls(p) - output is c("a", "b")
p$ls()

Here ls() is _not_ a special proto method but is just the ordinary
ls() provided by R.  $.proto calls ls() sticking in p as the first
argument.  A proto object is an environment and ls with a first
argument that is an environment lists the names of the objects in that
environment.  Similarly:

p$as.list()
p$str()
p$parent.env()
p$print()
p$eapply(length)

are the same as as.list(p), str(p), parent.env(p), print(p) and
eapply(p, length).

Although this might seem like its just syntax in proto it does allow
one to override the method.  For example,

p2 <- proto(a = 1, b = 2, print = function(.) with(., cat("a:", a,
"b:", b, "\n")) )

p2$print() # uses p2's print

print(p2) # uses R's print

etc.
4 days later
#
I just realized that even in the non-nested case, calling from top level 
"broke" some of my code "inexplicably", since the globally stored 
function call gets overridden if you call Curry again. So old Curried 
functions break.

Glad to have this fix.
On 5/25/2012 5:23 PM, Hadley Wickham wrote: