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:

(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

> (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.

> (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

(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.

(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.