arc> (let orig +
(def + args
(if (all acons args)
(do (prn "Blah blah, special, blah blah")
(apply orig args))
(apply orig args))))
*** redefining +
#<procedure: +>
arc> (+ 1 2 3)
6
arc> (+ '(a b c) '(d e f))
Blah blah, special, blah blah
(a b c d e f)
arc> (+ '(a b c) 1 2 3)
Error: "car: expects argument of type <pair>; given 1"
The only things you might have trouble extending this way are macros (since any definition up to the point where you redefine it will have been expanded)
arc> (mac m (x) `(do (prn "orig") ',x))
#(tagged mac #<procedure: m>)
arc> (def f () (m 5))
#<procedure: f>
arc> (let orig (rep m)
(mac m (x)
(if (number x)
`(do (prn "redef") ',x)
(orig x))))
*** redefining m
#(tagged mac #<procedure: m>)
arc> (m "a string")
orig
"a string"
arc> (m 5)
redef
5
arc> (f)
orig
5
and names treated specially by the compiler (if, fn, quote, quasiquote, unquote, unquote-splicing, assign, nil, t).
(let orig f
(def f args
; you can put ANY code you want in here
))
Hence, extend is independent from annotate: just as you can annotate without using extend, you can extend without using annotate. extend is just a byproduct of closures and first-class functions. Of course, combining annotate and extend lets you dispatch based on custom types.