Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> R and Lisp are hardly alike even if it was inspire by it. It's like saying Erlang and Prolog is very similar. If you want learn FP do it in Erlang, Lisp, Haskell, etc.. Don't do it in R, it's half baked.

They are very alike in the underlying core design, not in how you use them.

In R, everything is an expression, and every expression is a function call. Even things like assignments, if/else, or function definitions themselves, are function calls, with C-like syntactic sugar on top. You don't have to use that sugar, though! And all those function calls are represented as "pairlists", which is to say, linked lists. Exactly like an S-expr would - first element is the name being invoked, and the rest are arguments. And you can do all the same things with them - construct them at runtime, or modify existing ones, macro-style.

So in that sense, R is actually pretty much just Lisp with lazy argument evaluation (which makes special forms unnecessary, since they can all be done as functions), and syntax sugar on top. Where it really deviates is the data/object model, with arrays and auto-vectorization everywhere.



R certainly has a lispish code-as-data element to it, but it seems like it has some serious flaws. Don't most lisps have functions and macros as separate constructs? R has functions, but with some mucking around you can make them do macro-type stuff. Then people write these half-function, half-macro things (e.g. "non-standard evalation") that tend to break composability, either totally or sometimes only in edge cases.


Something like that would be called a FEXPR in Lisp.

https://en.wikipedia.org/wiki/Fexpr


Lisps do that distinction because they need it. In R, you can do everything with functions, because arguments can be lazily evaluated, or you can even get the syntax tree used for that argument at call site instead. So in R, a macro is just a function.

And yes, it's easy to break stuff that way. Just as easy as it is with macros (esp. non-hygienic ones).


> Lisps do that distinction because they need it.

Because of much better performance and predictability of code.

See: http://www.nhplace.com/kent/Papers/Special-Forms.html


I'm not saying it's a better way to do things. It trades having fewer primitives (and hence simpler language structure) for performance. But the use of lazy evaluation is pervasive in R in general, so it's a conscious design decision that they made.


Do you have an example of what R would look like without the C-like syntactic sugar? It doesn't need to be complex, I'm just intrigued about what it might look like.


Sure! If you want to experiment with this, it's pretty easy to "reverse engineer" that original form. Just use quote, and convert to a list (you need to do that because expressions will pretty print by default using the same sugar!), to see the internal structure:

   > as.list(quote(if (1 > 2) 3 else 4));
   [[1]]
   `if`

   [[2]]
   1 > 2

   [[3]]
   [1] 3

   [[4]]
   [1] 4
Okay, let's try this:

   > `if`(1 > 2, 3, 4)
   [1] 4
And to make sure that it really does evaluate only the correct branch:

   > `if`(1 > 2, cat(3), cat(4))
   4
Now something more interesting:

   > f <- as.list(quote(function(x, y=1) { x + y }));
   > f
   [[1]]
   `function`

   [[2]]
   [[2]]$x


   [[2]]$y
   [1] 1


   [[3]]
   {
       x + y
   }

   [[4]]
   function(x, y=1) { x + y }
This last entry is probably confusing, because it looks recursive. However, it's not the function itself - it's the srcref (basically, metadata about where the code came from, used e.g. by debugger to report line numbers) - it just pretty-printed itself like the function it is for. We can ignore it, though. Otherwise there are two arguments here - first one is a pairlist with named elements, one for each argument, and values are the default values for those arguments (if present). Second argument is the function body, which is itself an expression. We can look at that:

   > as.list(f[[3]])
   [[1]]
   `{`

   [[2]]
   x + y
So {} is itself a function! And x+y works as you'd expect:

   > as.list(f[[3]][[2]])
   [[1]]
   `+`

   [[2]]
   x

   [[3]]
   y
Now let's try to do the same ourselves. One catch here is that function() expects the first argument to be a list itself, rather than an expression that evaluates to a list. So we can't do this:

   > `function`(pairlist(x=1, y=2), quote({x + y}))
   Error: invalid formal argument list for "function"
Because the first argument is not itself a pairlist, but a promise of one. So we need to construct the call, thereby evaluating the arguments in advance, and then eval it. Here's the first take, ignoring the function body:

   > eval(call("function", pairlist(x=1, y=2), quote({x + y})))
   function (x = 1, y = 2) 
   {
       x + y
   }
The body we can just rewrite as plain calls:

   > eval(call("function", pairlist(x=1, y=2), quote(`{`(`+`(x, y)))))
   function (x = 1, y = 2) 
   {
       x + y
   }
And just to make sure it does what it should:

   > eval(call("function", pairlist(x=1, y=2), quote(`{`(`+`(x, y)))))(123)
   [1] 125
You might have noticed that I've cheated a bit here by giving each argument a default value - the original didn't have one for the first argument. It's because we need to somehow get a "missing" bit on a list element for that to work, and this makes it a great deal more convoluted - R has an easy way to check for it, but not to set it, other than by omitting arguments in function calls. The easiest way to get it is to quote() a call with one, and then just pull the pairlist out of the expression tree.


That's really awesome, thanks!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: