【问题标题】:IllegalStateException: Attempting to call unbound fn in macroIllegalStateException:尝试在宏中调用未绑定的 fn
【发布时间】:2014-08-12 06:50:32
【问题描述】:

我正在尝试编写一个调用某些函数的宏。这些函数只能由宏使用,所以我将它们放在一个 letfn 包装宏中。伪代码:

(letfn [(fn-a [] ...)
        (fn-b [] ...)
        (fn-c [] (fn-b))]
  (defmacro my-macro [stuff]
   `(let [whatever# (fn-a)]
      (fn-c))))

fn-afn-c 的调用有效,但是当fn-c 尝试调用fn-b 我得到IllegalStateException: Attempting to call unbound fn: #'name.space/fn-b时间>。这是为什么呢?

如果我将fn-bfn-c 放在它们自己的defns 中,一切正常。但我不想这样做,因为它不干净。

编辑:为了测试,我尝试将函数绑定放在内部 let 中,但遇到了同样的异常。

【问题讨论】:

    标签: macros clojure


    【解决方案1】:

    我认为这种方式根本行不通。 例如,对fn-c 的调用扩展为your.namespace/fn-c,因此您的代码似乎调用了恰好具有相同名称的其他函数。 但是你没有your.namespace/fn-b,这会引发异常。

    要引用不合格的符号,您需要引用和取消引用它:~'fn-a 但这也不起作用,因为局部函数没有在扩展点定义,您只能将它们用于宏本身。

    您要么必须在命名空间中定义函数并在宏中限定它们,要么将它们包含在宏扩展中,这将在每次使用时重新定义它们。

    【讨论】:

    • 我尝试将函数绑定放在内部 let 中,但遇到了同样的异常。所以我不认为letfn 有错。我也确信这些名字在代码的其他地方已经不存在了。
    • 如果你把它们放在内部 let 中,你应该为它们使用 gensym 名称,只需在它们的名称后面加上一个井号,例如:fn-a#。这样,名称会自动生成,并且不会与其他名称发生冲突。如果您在其他地方没有使用这些名称的函数,我真的想知道以前如何调用它们中的任何一个。如果您只想隐藏辅助函数,可以使用 defn- 将它们设为当前命名空间/文件的私有
    • 要调试宏的任何问题,请始终查看扩展代码。为此使用macroexpand-1macroexpandclojure.walk/macroexpand-all
    • 顺便说一句。不管你把定义放在哪里,如果你只是通过写他们的名字来调用它们,这些调用仍然会扩展为命名空间限定名。使用macroexpand,你会看到发生了什么。 (macroexpand-1 '(my-macro (stuff...)))
    • 我认为最干净的方法是将 fns 放在宏扩展中,所以我就是这样做的。谢谢
    【解决方案2】:

    我不确定这是否正是您所追求的,但如果我这样做:

    (letfn [(fn-a [] (println 1))
            (fn-b [] (println 2))
            (fn-c [] (fn-b))]
      (defmacro my-macro [stuff]  
        `(let [whatever# ~(fn-b)]
           ~(fn-c))))
    

    然后它就可以工作了——它只需要波浪号来取消对函数调用的引用。

    以下两项工作:

    (defn fn-x [] (println 1))
    
    (defn fn-y [] (fn-x))
    
    (defmacro my-macro2 [stuff]  
            `(let [whatever# (fn-x)]
               (fn-y)))
    

    (defn fn-x [] (println 1))
    
    (defn fn-y [] (fn-x))
    
    (defmacro my-macro2 [stuff]  
            `(let [whatever# ~(fn-x)]
               ~(fn-y)))
    

    在第一种情况下,函数在编译时被评估并且它们的结果被合并到宏中,而在第二种情况下,它们在运行时被评估。使用letfn(它本身是一个宏),结果在编译时不可用(可能是因为它们是在编译宏之后编译的),因此这些函数只能在运行时使用。

    【讨论】:

    • 你没有倒过来吗?在第一种情况下,函数在运行时被评估,而在第二种情况下,在编译时被评估。我尝试将函数绑定放在内部 let 中,但遇到了同样的异常。所以我不认为letfn 有错。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多