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.

The macro one

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:

 1 2 3 4 5 6 (re­quire syn­tax/parse/de­fine) (de­fine-sim­ple-macro (swap v i j) (let [(tmp (vec­tor-ref v i))] (vec­tor-set! v i (vec­tor-ref v j)) (vec­tor-set! v j tmp))) 

This even works, if I test it on the repl

 1 2 3 4 5 6 7 8 9 > (de­fine v (build-vec­tor 15 (λ(x) x))) > (dis­play v) #(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14) > (swap v 0 1) > (dis­play 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.

 1 2 3 4 5 6 7 8 9 > (de­fine v (build-vec­tor 15 (λ(x) x))) > (dis­play v) #(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14) > (swap v (ran­dom 15) 0) > (dis­play 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

 1 2 3 (let [(tmp (vec­tor-ref v (ran­dom 15))] (vec­tor-set! v (ran­dom 15) (vec­tor-ref v 0)) (vec­tor-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.

 1 2 3 4 5 6 7 8 (re­quire syn­tax/parse/de­fine) (de­fine-sim­ple-macro (swap v i j) (let* [(i_ i) (j_ j) (tmp (vec­tor-ref v i_))] (vec­tor-set! v i_ (vec­tor-ref v j_)) (vec­tor-set! v j_ tmp))) 

Bonus: The in­vis­i­ble hy­phen 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.