While playing around with the code for my profiler (http://www.arclanguage.org/item?id=16090), a pattern kept cropping up. I kept wanting to define a function f using another function g, then later redefine g while having f still work the same way. E.g., arc> (def foo (x y)
(isa x y))
#<procedure: foo>
arc> (def isa (x y)
(prn "redefined!! <-- want to avoid this when calling foo")
(is (type x) y))
*** redefining isa
#<procedure: isa>
arc> (foo 'x 'sym)
redefined!! <-- want to avoid this when calling foo
t
This was annoying, because while I could do the old closure trick arc> (let orig-isa isa
(def foo (x y)
(orig-isa x y)))
#<procedure: foo>
arc> (= isa 'something-dumb)
something-dumb
arc> (foo 1 'int)
t
it involves manually changing all the uses of a particular function in the body of my definition. Plus, macros often expand into unforeseen function calls that I probably won't notice I have to replace when doing my manual intervention.So, I got to wondering how I could do this automatically. Here's the macro I came up with: (mac hygienic exprs0
(withs (exprs (map macex-all exprs0) ; see bitbucket.org/fallintothis/macdebug
fns (keep [and (isa _ 'sym) (bound _) (isa (eval _) 'fn)]
(dedup (flat exprs)))
uniqs (map [uniq] fns))
`(with ,(mappend list uniqs fns)
,@(map [do
(each (old new) (map list fns uniqs)
(= _ (tree-subst old new _)))
_]
exprs))))
Thus, (hygienic
(def foo (x y)
(isa x y)))
expands every macro and (rather un-delicately) replaces any symbol in the (def ...) that's a function name with a lexically-bound gensym, giving us (with (gs1751 sref gs1752 bound gs1753 disp gs1754 stderr gs1755 isa)
((fn ()
(gs1751 sig '(x y) 'foo)
((fn ()
(if (gs1752 'foo)
((fn ()
(gs1753 "*** redefining "
(gs1754))
(gs1753 'foo (gs1754))
(gs1753 #\newline (gs1754)))))
(assign foo
(fn (x y) (gs1755 x y))))))))
In action: arc> (hygienic (def foo (x y) (isa x y)))
#<procedure: foo>
arc> (foo 'x 'sym)
t
arc> (= isa 'something-dumb)
something-dumb
arc> (foo 'x 'sym)
t ; look ma, no error!
Unfortunately, function definitions still get in the way (and I don't think there's much we can do about it). In this example, since isa is defined by (def isa (x y) (is (type x) y))
we can still clobber is and foo will be none-the-wiser: arc> (hygienic (def foo (x y) (isa x y)))
#<procedure: foo>
arc> (let old-isa isa
(def reset () (= isa old-isa))
(= isa 'something-dumb))
something-dumb
arc> (foo 'still 'works)
nil
arc> (reset)
#<procedure: isa>
arc> (= is 'something-dumb)
something-dumb
arc> (foo 'doesnt 'work)
Error: "Function call on inappropriate object something-dumb (sym work)"
Fun experiment, though. |