Clojure 为什么';t工作:“;defn的第一个参数必须是符号;
为什么会出现错误:Clojure 为什么';t工作:“;defn的第一个参数必须是符号;,clojure,Clojure,为什么会出现错误: IllegalArgumentException First argument to defn must be a symbol clojure.core/defn (core.clj:277) 当我尝试定义这样的函数时: (defn (symbol "f[]") 1) (defn (symbol "f") [] 1) 或者像这样: (defn (symbol "f[]") 1) (defn (symbol "f") [] 1) 为什么这些不等同于下面的直截了当的
IllegalArgumentException First argument to defn must be a symbol clojure.core/defn (core.clj:277)
当我尝试定义这样的函数时:
(defn (symbol "f[]") 1)
(defn (symbol "f") [] 1)
或者像这样:
(defn (symbol "f[]") 1)
(defn (symbol "f") [] 1)
为什么这些不等同于下面的直截了当的例子
(defn f [] 1)
我知道这很深奥:但我突然想到,我可能想在某个时候动态命名一个函数。(这里没有真正的用例-只是试图理解Clojure的想法…当您将参数传递给宏时,它们不会事先进行评估。由于
defn
是一个宏,因此在这两种情况下传递的参数是不等价的。将参数传递给宏时,它们不会事先进行计算。因为defn
是一个宏,所以在这两种情况下传递它的内容是不等价的。您是在混合代码和数据。这是一个非常常见的错误。例如
(+ 4 5) ; ==> 9
('+ 4 5) ; ==> Error
”+
计算为符号。它与变量+
不同,后者是对函数进行编码和计算的变量。通过评估它们很容易进行检查:
+ ; ==> #<core$_PLUS_ clojure.core$_PLUS_@312aa7c>
'+ ; ==> +
因此,发生的情况是,在代码运行之前,(取笑“f”1)
被替换为(clojure.core/defn f[]1)
,运行时永远看不到它来自何处。虽然这看起来很有用,但您仍然不能使用绑定或输入来生成函数:
(def fun-name "f")
(def fun-value 1)
(macroexpand-1 '(make-fun fun-name fun-value))
; ==> (clojure.core/defn fun-name [] fun-value)
宏只是简化和抽象语法的一种方式。如果您总是编写一个类似于(defn name[&args](let…)的模式
您可以在宏中创建不同绑定的部分,并缩短新宏使用抽象的每个位置。这是一种代码翻译服务。在编译时,参数只是支持替换的文字代码,而且您从一开始就没有机会查看变量或表达式是否具有特定值ly知道代码,但从不知道它们实际代表什么。因此,当最终结果中的代码运行时,通常会出现错误
最后,你可以在运行时使用eval
做任何事情。在我19年的专业程序员生涯中,我已经两次看到eval
以合理的方式被使用。你可以做:
(defn make-fun [name value]
(eval `(defn ~(symbol name) [] ~value)))
(make-fun fun-name fun-value)
; #'user/f
(f)
; ==> 1
现在,虽然这样做是可行的,但你不应该这样做,除非这是一种用于测试或处理代码的工具,而不是作为服务运行的代码的一部分,字符串来自不安全的源。我会选择使用字典,这样你就不会更新自己的环境。想象一下,如果输入是
取笑
或者你的代码的其他部分,让客户控制你的软件。你在混合代码和数据。这是一个非常常见的错误
(+ 4 5) ; ==> 9
('+ 4 5) ; ==> Error
'+
求值为一个符号。它与变量+
不同,变量+
是一个函数的编码和求值。通过求值可以轻松检查:
+ ; ==> #<core$_PLUS_ clojure.core$_PLUS_@312aa7c>
'+ ; ==> +
因此,在代码运行之前(取笑“f”1)被替换为(clojure.core/defn f[]1)
,运行时永远看不到它来自何处。虽然这似乎很有用,但您仍然无法使用绑定或输入来生成函数:
(def fun-name "f")
(def fun-value 1)
(macroexpand-1 '(make-fun fun-name fun-value))
; ==> (clojure.core/defn fun-name [] fun-value)
宏只是简化和抽象语法的一种方式。如果您总是编写一个类似于(defn name[&args](let…)的模式
您可以在宏中创建不同绑定的部分,并缩短新宏使用抽象的每个位置。这是一种代码翻译服务。在编译时,参数只是支持替换的文字代码,而且您从一开始就没有机会查看变量或表达式是否具有特定值ly知道代码,但从不知道它们实际代表什么。因此,当最终结果中的代码运行时,通常会出现错误
最后,你可以在运行时使用eval
做任何事情。在我19年的专业程序员生涯中,我已经两次看到eval
以合理的方式被使用。你可以做:
(defn make-fun [name value]
(eval `(defn ~(symbol name) [] ~value)))
(make-fun fun-name fun-value)
; #'user/f
(f)
; ==> 1
现在,虽然这样做是可行的,但你不应该这样做,除非这是一种用于测试或处理代码的工具,而不是作为服务运行的代码的一部分,字符串来自不安全的源。我会选择使用字典,这样你就不会更新自己的环境。想象一下,如果输入是
开玩笑
或者让客户端控制软件的代码的其他部分。答案是Josh所说的(defn
是一个宏;如果它是一个函数,那么你的代码真的会以这种方式工作)。您可以定义自己的defn
变量宏,该宏将执行您想要的操作,或者只使用eval
:
(eval `(defn ~(symbol "f") [] 1))
; => #'user/f
(f)
; => 1
答案是Josh所说的(
defn
是一个宏;如果它是一个函数,那么你的代码真的会以这种方式工作)。你可以定义你自己的defn
变量宏来做你想做的事,或者只使用eval
:
(eval `(defn ~(symbol "f") [] 1))
; => #'user/f
(f)
; => 1
您真的不需要使用
eval
您遇到了一个名为“”的问题。一旦您尝试将宏视为函数(例如,可能将其传递给map
),您会发现,如果不编写另一个宏,您将无法执行此操作。宏#2等也是如此
因此,你不能像编写函数那样编写宏。这就是一般建议的由来,“当你可以使用函数时,千万不要使用宏。”
在本例中,defn
是一个宏,因此您别无选择,只能编写另一个宏(def
的行为方式相同,即使它是一种特殊形式而不是宏)。我们的新宏dyn defn
从字符串列表中动态创建函数名:
(defn fun-1 [] 1)
(def fun-2 (fn [] 2))
; (def (symbol (str "fun" "-3")) (fn [] 3))
; => Exception: First argument to def must be a Symbol
(defmacro dyn-defn
"Construct a function named dynamically from the supplied strings"
[name-strs & forms]
(let [name-sym (symbol (str/join name-strs)) ]
(spyx name-sym)
`(defn ~name-sym ~@forms)))
(dyn-defn ["fun" "-3"]
[]
3)
结果:
*************** Running tests ***************
:reloading (tst.demo.core)
name-sym => fun-3 ; NOTE: this is evaluated at compile-time
Testing _bootstrap
-------------------------------------
Clojure 1.9.0 Java 1.8.0_161
-------------------------------------
Testing demo.core
Testing tst.demo.core
(fun-1) => 1 ; NOTE: these are all evaluated at run-time
(fun-2) => 2
(fun-3) => 3
请注意,函数名是defn
宏的参数,必须是符号,而不是函数调用
注:
正确,您无法通过查看表单来判断表单是“调用”函数还是宏。