Hacker News new | past | comments | ask | show | jobs | submit login

Yes. It's part of good Lisp style not to use a macro when a function would suffice, and yet the source of HN is full of macro calls. If you want to see examples of modern uses of macros, that would be a good place to start.

http://ycombinator.com/arc/arc3.tar

If you want a single, self-contained example, here's one:

    (mac zap (op place . args)
      (with (gop    (uniq)
             gargs  (map [uniq] args)
             mix    (afn seqs
                      (if (some no seqs)
                          nil
                          (+ (map car seqs)
                             (apply self (map cdr seqs))))))
        (let (binds val setter) (setforms place)
          `(atwiths ,(+ binds (list gop op) (mix gargs args))
             (,setter (,gop ,val ,@gargs))))))
How do you implement that in a language without macros?



Care to explain it for the Lisp-impaired?


It lets you apply an arbitrary function to a variable or a position anywhere within some structure:

    arc> (= flag t)
    t
    arc> (zap no flag)
    nil
    arc> flag
    nil
    arc> (= foo '(1 2 3 4 5))
    (1 2 3 4 5)
    arc> (zap (fn (x) (* x 10)) 
              (foo 0))
    10
    arc> foo
    (10 2 3 4 5)
BTW, in more idiomatic Arc you'd say

    (zap [* _ 10] foo.0)
Here's a line in HN that uses it:

    (zap [firstn votewindow* _] (uvar user votes))
This expands into roughly the equivalent of:

    (= ((profs* user) 'votes)
       (firstn votewindow* ((profs* user) 'votes)))
Not quite, though, because you only want to evaluate the code that gets you to the location once, and the operation needs to be atomic. The actual macroexpansion is much more complicated.


If that's all it does, then I don't see a problem with implementing it in any language without macros. Let's take simple C. (I didn't test it, or even try to compile - just want to show the idea)

    void zap(void *object, void* (*selector)(void *), void (*action)(void*)) {
        action(selector(object));
    }
Given your explanation: "It lets you apply an arbitrary function[1] to (a variable or a position anywhere within some structure)[2]."

Where object is the structure you're operating on, `selector` is a function taking an object and returning a pointer to the position [2]. The `action` is a pointer to an arbitrary function [1].

Your first example would look like this:

    int foo[5] = {1, 2, 3, 4, 5};
    void *select_first(void *x) {return x;}
    void act_times_10(void *x) {int *i = (int *)x; (*i)*=10;}
    
    zap(foo, select_first, act_times_10);
Or am I missing something you didn't include in the description?


By using C you are changing the problem, as your C pointer passes by reference instead of value. The lisp macro works equally well when passing by value, so try rewriting your function to operate on a struct (and, not a struct pointer). It is likely that PG was referring to this functionality when asking how to implement this as a function.


I think you're missing the fact that in the C version you have to write a specialized selector function each time (to find the thing you want to zap), whereas the macro works generically on any place. ("Place" here is an abstraction meaning "settable thing" that is closely related to Lisp macros and is analogous to the left-hand-side of assignment statements in most languages, but far more manipulable by the programmer.)


PG asked "How do you implement that in a language without macros?". I responded with a simplest translation of the example, but a more general solution exists too. Nothing stops me from using GObject instead of void*, which can provide named "places" and as much generic functionality as needed. `selector` and `action` can even operate on property tables, which would be a closer translation from lisp.

I was simply surprised that PG asked about something like that - I still think it's pretty simple and the code in C is as close to the macro as possible (with whatever object abstraction you need).

However I have to admit that I thought about other languages now and it seems that the described zap() is not possible in python... and that's interesting / worrying:

    action(object[place])
cannot assign a new value to `object[place]`, while

    object[place]=action(object[place])
has to do the lookup twice.


To be consistent across the lisp and c code, I just want to point out that the definition of the c code should be:

  void zap(*action, *selector, *object) { ... }
compared to

  (mac zap (op place . args) ... )
But I also think what the c code really represents is an abstraction. You still need to write out the definitions of the functions each time you call zap. With the lisp code you write one line, which is the whole point of macros: write a single line of code which expands to several lines.


I am suspicious that this macro would not be necessary in a language with better libraries. For example, this code:

  (zap [firstn votewindow* _] (uvar user votes))
In Python you wouldn't need a macro for this. You could just write

  del uvar(user, votes)[votewindow:]
or

  uvar(user, votes)[votewindow:] = []


I think this highlights a weakness of Arc -- while terseness is probably wonderful once one has Arc in one's brain, it makes things considerably less legible to everyone else. Contrast with presenting CL/Scheme to proponents of the other, where I would assume they could mostly muddle through, Googling one or two odd things. Have you ever tried Googling "arc no function"? (Lisp analogy may not be accurate; substitute Python/Ruby or C#/Java if it makes you feel better.)


C++ version:

  template<typename T>
  void zap (T (*f)(T x), T &x) { x = f(x); }
Example:

  int square (int x) { return x*x; }

  int foo[5] = {1,2,3,4,5};

  zap<int>(square, foo[3]);   // foo is now {1,2,3,16,5}
Admittely, the <int> is a little ugly, but that can probably be fixed with Hindley-Milner (or just plain dynamic typing).


C++ templates are similar to macros: they run at compile time to generate code, but they're a lot harder to use.


True, but in this case, the template serves a different purpose from the macro. It's there to make zap polymorphic, and would be unnecessary in a dynamic or type-inferred language. The main point is that pg's macro can be implemented as an ordinary function in a language that has references.


"Scrap Your Boilerplate" in Haskell: http://www.cs.vu.nl/boilerplate/

You can do the same thing without macros. Granted, the compiler supports by generating code, so it is macro-esque.




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

Search: