Arc Forumnew | comments | leaders | submitlogin
Why is "do" a macro and not a function?
3 points by Pauan 3621 days ago | 4 comments
For years now, I've taken it for granted that "begin" in Scheme and "do" in Arc are macros. In Nulan, "do" is also a macro, and it expands to a JavaScript semicolon/comma.

If I recall correctly, Scheme does not guarantee the evaluation order of arguments, so that's a good reason for Arc to define "do" as a macro.

In addition, because functions in Arc can have multiple expressions for their body, it's very easy to write "do" as a macro.

But in Nulan, that isn't the case. Arguments are always evaluated left-to-right, and functions can only have a single expression as their body (which is why I need "do" in the first place!).

So, I'm going to try defining "do" as a function that takes any number of arguments and simply returns its last argument.

But I wonder if this is a good idea. Perhaps I'm missing something. Is there some important reason why "do" needs to be a macro?



4 points by waterhouse 3618 days ago | link

The first (and only) reason that comes to my mind is: efficiency. Calling a function that takes rest args means allocating a list at runtime; the usual expansion for "do" is a direct call to a lambda, which the Racket compiler seems able to handle with no runtime cost. Thus:

  arc> (def do-func args last.args)
  #<procedure: do-func>
  arc> (mac do-macro args `((fn () ,@args)))
  #(tagged mac #<procedure: do-macro>)
  arc> (time:repeat 1000000 (do-func 1 2 3))
  time: 270 cpu: 270 gc: 6 mem: -4392856
  nil
  arc> (time:repeat 1000000 (do-macro 1 2 3))
  time: 69 cpu: 70 gc: 0 mem: 928
  nil
  arc> (time:repeat 1000000 1 2 3)
  time: 67 cpu: 68 gc: 0 mem: 128
  nil
It is not too far-fetched an idea for a compiler to eliminate the runtime overhead of the calls to "do-func". Basically this would require assuming that "do-func" will not be redefined at runtime--which is permitted in Arc, so it would require the compiler to make optimistic assumptions and be prepared to invalidate the code when they are violated (i.e. if someone actually does redefine "do-func" at runtime). I've heard the best Javascript engines do this, but Racket does not.

In your use case, having a "do" that works at all is better than none, and I don't think there is any logical problem with doing so. (Strictly speaking, it makes it possible to "do" [!] more things, since you can't apply a macro.)

-----

2 points by Pauan 3617 days ago | link

That's a good point. In Nulan, you can't redefine things (all variables are constant). I'm not so worried about performance right now. My main concern is simplicity, since implementing "do" as a macro introduces a lot of extra complexity (because Nulan compiles to JavaScript).

-----

2 points by akkartik 3621 days ago | link

So you mean (def newdo args)? My mind is blown.

-----

3 points by Pauan 3621 days ago | link

Well, no, because "do" returns the last argument, so it would be:

  (def do args (last args))

-----