Arc Forumnew | comments | leaders | submitlogin
4 points by absz 6122 days ago | link | parent

The biggest win, which actually results from a semantic change, is that it becomes possible to cleanly apply a macro to a variable number of arguments. Consider

  (apply and (acons x) (car x) lst)
as opposed to

  (and (acons x) (car x) @lst)
. The first version is broken: it will be forced to evaluate (acons x), (car x), and lst before applying and to them, which breaks the semantics of and: (acons x) no longer guards against (car x) trying to take the car of a non-list, as everything is evaluated before being passed to apply.

On the other hand, the second version works: the list is spliced in and then the and macro is run as normal. The semantics are preserved here because @, like a macro, expands the code first; nothing is evaluated, and then the and macro runs on its arguments as usual, short-circuiting if (acons x) fails. The variable being spliced is still evaluated, but that is the point of this notation.

As a concrete example (though I think this is generally useful/powerful), partial application then becomes merely

  (def par (fn . args)
    (fn newargs (fn @args @newargs)))
. If apply were used instead of the splicing @, this would actually break on certain macros as noted above, both causing the abstraction to leak and preventing someone from partially applying and, or, etc.


2 points by bogomipz 6114 days ago | link

The more I think about this, the more mind boggling I find it. What exactly would this expression expand to?

  (and (acons x) (car x) @lst)
The actual lst is only available at runtime, while the 'and macro expands at compile time.

  Edit:
After looking at the definition of 'and, it is clear that the above could never work. Without performing the splice it would expand to

  (if (acons x) (if (car x) @lst))
For each additional argument 'and must add another 'if to the code, so with lst being spliced in at runtime, we have an impossible problem.

It seems @ can only ever work reliably in quasiquote. I guess longtime lispers already knew, otherwise we would probably have had this feature for several decades already.

If you insist, it could still be done for functions. To avoid inefficient code, it better be smart about how it builds the list;

  (foo 'a @lst)     -> (apply foo (cons 'a lst))
  (foo 'a 'b @lst)  -> (apply foo (append (list 'a 'b) lst))
  (foo @lst 'c 'd)  -> (apply foo (append lst (list 'c 'd)))
  (foo @lst 4 @bar) -> (apply foo (append lst (cons 4 bar)))
and so on.

-----

1 point by absz 6110 days ago | link

I had assumed that (and (acons x) (car x) @lst) would expand to (and (acons x) (car x) lst.0 lst.1 ... lst.N), and only then would it expand to (if (acons x) (if (car x) (if lst.0 ...))). In other words, @s are expanded as though they were the outermost macro, not the innermost. That should remove the problem of 'and having to be psychic. Your point about efficiency, however, is a very good one.

-----

2 points by bogomipz 6109 days ago | link

As mentioned, the macro expands at compile time, but the value of lst is not available until runtime.

In order to expand a call to 'and with 6 sub expressions into (if e0 (if e1 (if e2 (if e3 (if e4 e5))))), all 6 expressions must be visible at compile time.

Splicing in a list of arguments to a macro will simply never work (except by compiling the code from scratch each time you run it, i.e. limiting yourself to the most inefficient of interpreter techniques)

-----

1 point by absz 6109 days ago | link

Aha! Oh, I see. I should have realized that. Yes, that's a problem :) I'm not convinced it's insurmountable, but this syntax certainly won't work. Thank you.

I still, however, think it might be nice for functions, but that's merely a difference in appearance, not functionality.

-----