Arc-macros, baby. Arc-macros.
OK, this is not really about macros, it's really a reader hack, but bear with me... :-)
Let's implement Arc's "negation" operator (~) in Chicken. It's only a few lines of code.
(define (negate pred)
(lambda (x)
(not (pred x))))
(set-read-syntax! #\~
(lambda (port)
(let ((expr (read port)))
(list 'negate expr))))
That's all. Test test... (Code below uses SRFI-1 for filter.)
> (use srfi-1) ; loading library srfi-1 ... > (filter ~odd? '(1 2 3 4 5 6 7 8)) (2 4 6 8) > (filter ~(lambda (x) (> x 2)) '(0 1 2 3 4 5 6)) (0 1 2)
This probably falls in the category "don't try this at home". But if you're coming from languages where this just isn't possible, this kind of stuff is really cool.
Note #1: you can just use (negate odd?) and such, which is slightly longer but probably cleaner. And that kind of thing would work in Python as well. (Although function composition isn't all that popular in Python-land.)
Note #2: This implementation just reads the expression that follows ~, then feeds it to negate. So using a lambda works as well (but kind of defeats the purpose, which is conciseness).
By the way, the reader extension I showed a few days ago (to add Awk-like $N behavior) works, but I found out that it's not really how you're supposed to write it. At the point something like $3 is read, we're *reading* rather than *evaluating*, so it should really expand to something that returns the right result *when evaluated later*. So better code would be:
(set-read-syntax! #\$
(lambda (port)
(let* ((s (read-number port))
(i (string->number s)))
(list 'field i))))
In other words:
- when the reader encounters $3, it expands it to the form (field 3) (but *does not* evaluate it at that point)
- later on, we evaluate (field 3) and get the correct result, using whatever is in *fields* at the time of evaluation
So yeah, maybe this is like macros after all, but I don't know the correct term. :-)
There's another post like this coming up, using Arc as an excuse to tinker with the Scheme reader.
Update (2008-02-04): Here is similar code in Python, sort of. The additional "syntax" (which is actually, creating a class that defines "~", and then wrapping functions in it) seems to be more trouble than it's worth. If you do this kind of thing a lot in Python (probably not, but you never know), you might be better off using a simple negate function, e.g.
def negate(f):
return lambda *args, **kwargs: not f(*args, **kwargs)
(I normally would not have used lambda, but after more than a month of Scheming, it's hard not to. :-)
John Cowan said,
January 31, 2008 @ 3:22 pm
Read macros like $ and ~ are expanded at read time, (ordinary) macros are expanded at compile time (even the interpreter "compiles" at least to that extent).
Hans Nowak said,
January 31, 2008 @ 4:08 pm
But, so it's correct to call them "read macros"?
Konrad said,
February 5, 2008 @ 6:06 am
> The additional "syntax" (which is actually, creating a class that
> defines "~", and then wrapping functions in it) seems to be more
> trouble than it's worth.
Absolutely - it's not very pythonic to introduce some magic like this. Still, it was an interesting excercise.