Can anyone explain why I can rebind开发者_Go百科 list but not +?
(binding [list vector]
(list 1 3))
(binding [list +]
(list 1 3))
(binding [+ list]
(+ 1 3))
I'd like to rebind + so I can do partial evaluation.
In Clojure 1.1.0 at least, +
with two arguments is inlined for performance. Your binding happens too late. With more arguments it works differently.
Clojure 1.1.0-master-SNAPSHOT
user=> (binding [+ -] (+ 1 2))
3
user=> (binding [+ -] (+ 1 2 3))
-4
One workaround is to make your own namespace and shadow clojure.core/+
with your own function.
user=> (ns foo (:refer-clojure :exclude [+]))
nil
foo=> (defn + [& args] (reduce clojure.core/+ args))
#'foo/+
foo=> (+ 1 2)
3
foo=> (binding [+ -] (+ 1 2))
-1
Note that inlining appears to happen even more aggressively in the current snapshot of Clojure 1.2.0.
Clojure 1.2.0-master-SNAPSHOT
user=> (binding [+ -] (+ 1 2))
3
user=> (binding [+ -] (+ 1 2 3))
6
It may be wisest to use a function name other than +
, e.g. add
, to avoid confusion.
Quick workaround: use let instead of binding and this will work for you just fine:
user=> (let [+ list] (+ 2 3))
(2 3)
A little (incomplete) digging into the reason:
Take a look at the source for the + function:
(defn +
"Returns the sum of nums. (+) returns 0."
{:inline (fn [x y] `(. clojure.lang.Numbers (add ~x ~y)))
:inline-arities #{2}}
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (add x y)))
([x y & more]
(reduce + (+ x y) more)))
Notice that there are several inline function definitions for different numbers of arguments. If you try to rebind the 0 or 1 arity definitions, it works just fine:
user=> (binding [+ (fn [] "foo")] (+))
"foo"
user=> (binding [+ (fn [a] (list a))] (+ 1))
(1)
Now, this definitely doesn't work (as you discovered) for the 2-argument case. I'm not quite connecting the dots, but the . (special form) makes me suspicious combined with binding being a macro whereas let is a special form...
The metadata specially calling out arity 2 also seems suspicious.
精彩评论