4 ms·
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 exampl
by pg 17y ago
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 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?
- jganetsk 17y ago"Scrap Your Boilerplate" in Haskell: http://www.cs.vu.nl/boilerplate/ 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.
- rbanffy 17y agoCare to explain it for the Lisp-impaired?
- pg 17y agoIt 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.
- viraptor 17y agoIf 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?
- dbul 17y agoTo 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.
- gruseom 17y agoI 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.)
- viraptor 17y agoPG 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.
- swolchok 17y agoI 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.)
- deleted 17y ago[deleted]
- rkts 17y agoC++ 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).
- stefano 17y agoC++ templates are similar to macros: they run at compile time to generate code, but they're a lot harder to use.
- rkts 17y agoTrue, 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.