Inconsistency in Clojure: functions in macros and IllegalArgumentException

前端 未结 2 918
广开言路
广开言路 2020-12-30 08:06

Two following examples of using a function in a macro result in evaluations without errors.

(defmacro works []
  (let [f (fn [] 1)]
    `(~f)))
(works)
;; =&         


        
相关标签:
2条回答
  • 2020-12-30 08:32

    See this example that does also throw the exception:

    (defmacro does-also-not-work []
      (let [x 4
            f (fn [] x)]
        `(~f)))
    

    Just like the result of constantly, but unlike your first two examples, f here is a closure. Apparently, closures created during macro-expansion time do not persist.

    0 讨论(0)
  • 2020-12-30 08:52

    Take a look at clojure.lang.Compiler.ObjExpr#emitValue(). Any instance objects which appear directly in code (or generated code, in the case of macro-expansion results) must either:

    • Be of a type compiler knows how to instantiate or emit a reference to; or
    • Have print-dup defined for their type, in which case the compiler emits object instantiation via round-tripping through the reader.

    Function objects do have a print-dup implementation, but it constructs read-eval forms which only call the 0-argument version of the function class constructor:

    (print-dup (fn [] 1) *out*)
    ;; #=(user$eval24491$fn__24492. )
    (let [x 1] (print-dup (fn [] x) *out*))
    ;; #=(user$eval24497$fn__24498. )
    

    Clojure closures are implemented via function-classes which accept their closed-over variable values as constructor arguments. Hence:

    (let [f (fn [] 1)] (eval `(~f)))
    ;; 1
    (let [x 1, f (fn [] x)] (eval `(~f)))
    ;; IllegalArgumentException No matching ctor found ...
    

    So now you know, and know why to avoid directly inserting function objects into generated code, even when it "works."

    0 讨论(0)
提交回复
热议问题