Shooting myself in the foot with Racket macros.
by Markus Pfeiffer
So it begins: I finally started implementing permutation group algorithms in Racket. In good programmer tradition, I seem to find all the ways to stumble.
Since macros are cool, I thought I’d implement a macro to swap two entries in a vector. The question whether I should be using a macro here is beside the point. Like so:
(require syntax/parse/define)
(define-simple-macro (swap v i j)
(let [(tmp (vector-ref v i))]
(vector-set! v i (vector-ref v j))
(vector-set! v j tmp)))
This even works, if I test it on the repl
> (define v (build-vector 15 (λ(x) x)))
> (display v)
#(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
> (swap v 0 1)
> (display v)
#(1 0 2 3 4 5 6 7 8 9 10 11 12 13 14)
Except, if you know how macros work which I should have, I attended the brilliant Racket school, and we were warned about it, you will find this macro has a bug.
I needed to do to make random permutations, so I’d do something like this.
> (define v (build-vector 15 (λ(x) x)))
> (display v)
#(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
> (swap v (random 15) 0)
> (display v)
#(10 1 2 3 4 5 6 7 0 9 10 11 12 13 14)
Now this is odd isn’t it? It looks odd until you realise that macro expansion just replaces every occurrence of i
in the macro by (random 15)
, and hence the above call expands into
(let [(tmp (vector-ref v (random 15))]
(vector-set! v (random 15) (vector-ref v 0))
(vector-set! v 0 tmp))
Which is most certainly incorrect. The solution is to make sure that arguments are only evaluated once, and everything is good.
(require syntax/parse/define)
(define-simple-macro (swap v i j)
(let* [(i_ i)
(j_ j)
(tmp (vector-ref v i_))]
(vector-set! v i_ (vector-ref v j_))
(vector-set! v j_ tmp)))
Bonus: The invisible hyphen one
While writing this post, I tried copy-and-pasting code into my DrRacket session to make sure it works, and DrRacket kept complaining at me about define-simple-macro
not being an identifier. Turns out that I setup pollen to process the whole page with hyphenate which inserts invisible hyphens into all the things, including the code snippets. Damn you unicode.