A cautious tale how having mutable data structure caused a curious bug.
While working on one of the macros for Tiny Rule Engine, I had code like following inside a macro:
(setv uniques (->> (filter (fn [x] (and (not (symbol? x)) (= (first x) 'unique))) body) (map (fn [x] (tuple (rest x)))) (tuple)))
Idea was to process body parameter and:
- Find all expressions that start with symbol unique
- Drop the unique from start of such expressions and create a tuple of rest
- Create tuple containing all of these tuples
This is common use case for threading macro, as it allows writing data transformation pipeline without tons of nesting.
But it wasn’t working. I kept getting an error “HyMacroExpansionError: expanding `rule’: TypeError(‘tuple() takes at most 1 argument (2 given)’,)”. For some odd reason, tuple function was getting two parameters instead of one. To make things stranger, similar code inside a function worked without any problems.
As I couldn’t figure out why this was, I opened an issue at Hy’s issue tracker. After some investigation it was rather quickly concluded that the problem partly was how threading macro had been written. It modified expressions to create nested code from the threaded one. But because how it was modifying expressions, it ended up duplicating parameter.
Pull request has been opened that will correct the problem. We’re also discussing if objects used to represent symbols and expressions should be immutable. With immutable data it’s much harder to run into such a problem. This probably would require considerable amount of work to rewrite all Hy macros that modify code, not to mention all the internal changes into compiler.