为什么我会收到错误:
IllegalArgumentException First argument to defn must be a symbol clojure.core/defn (core.clj:277)
当我尝试定义这样的函数时:
(defn (symbol "f[]") 1)
或者像这样:
(defn (symbol "f") [] 1)
为什么这些不等同于下面的直接示例?
(defn f [] 1)
我知道这是深奥的:但我刚想到我可能想在某个时刻动态命名一个函数。 (这里没有真正的用例 - 只是想了解Clojure的想法......)
答案 0 :(得分:4)
将参数传递给宏时,不会事先评估它们。由于defn
是一个宏,因此在这两种情况下您传递的内容并不相同。
答案 1 :(得分:2)
您正在混合代码和数据。这是一个非常常见的错误。例如。
(+ 4 5) ; ==> 9
('+ 4 5) ; ==> Error
'+
评估为符号。 不与变量+
相同,它是代码并为函数求值。通过评估它们很容易检查:
+ ; ==> #<core$_PLUS_ clojure.core$_PLUS_@312aa7c>
'+ ; ==> +
defn
是一个扩展为def
的宏,因此您可以使用def
。 (def (symbol "x") 5)
不起作用的原因是因为def
在编译时发生。永远不会评估第一个参数,但是用于对同一名称空间中相同标识符的所有引用。像(symbol "x")
这样的表达式不会起作用,因为同样的原因+
和'+
无法混合。您可以在编译时执行此操作:
(defmacro make-fun [name expression]
`(defn ~(symbol name) [] ~expression))
(macroexpand-1 '(make-fun "f" 1))
; ==> (clojure.core/defn f [] 1)
(make-fun "f" 1)
; ==> #'user/f
(f) ; ==> 1
所以发生的事情是在代码运行之前(make-fun "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 ...)
的模式,你可以在宏中创建不同绑定的部分,并缩短每个地方使用新宏的抽象。它是一种代码翻译服务。在编译时,参数只是它被替换的文字代码,你永远无法看到变量或表达式是否具有某个值,因为你只知道代码而不知道它们实际代表什么。因此,错误通常在最终结果中的代码运行时出现。
最后,您可以使用eval
在运行时执行任何操作。在我19年的职业程序员运行中,我已经看到eval
以合理的方式使用了(defn make-fun [name value]
(eval `(defn ~(symbol name) [] ~value)))
(make-fun fun-name fun-value)
; #'user/f
(f)
; ==> 1
。你可以这样做:
make-fun
现在虽然这样做但你不应该这样做,除非这是一种测试或用代码做某事的工具,而不是它作为服务运行的代码的一部分,字符串来自不安全的资源。我会选择使用字典,这样您就不会更新自己的环境。想象一下,如果输入是@app.route('/user/user-account.html', methods=['GET', 'POST'])
@login_required
def cna_account():
if valid_user() == True:
result = get_user_account()
return render_template('user/user-account.html', result=result)
else:
return redirect(url_for('index'))
或代码的其他部分,可以让客户端控制您的软件。
答案 2 :(得分:2)
答案是Josh所说的(defn
是一个宏;如果它是一个函数,那么你的代码真的会以这种方式工作)。您可以定义自己的defn
变体宏,它可以执行您想要的操作或仅使用eval
:
(eval `(defn ~(symbol "f") [] 1))
; => #'user/f
(f)
; => 1
答案 3 :(得分:0)
您真的不需要使用eval
。
您遇到了称为&#34; turtles all the way down&#34;的问题。一旦你尝试将宏视为一个函数(例如,可能将它传递给map
),你会发现如果不编写另一个宏就不能这样做。这同样适用于宏#2等。
因此,您既可以编写宏,也可以编写函数。这是一般建议的起源,&#34;当你可以使用某个功能时,永远不要使用宏。&#34;
在这种情况下,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
宏的参数,必须是符号,而不是函数调用。
正确,你可以通过查看表格是否&#34;呼叫&#34;函数或宏。事实上,许多&#34;内置&#34; Clojure的功能是从语言的更基本部分构建的,无论是when
(source code)等宏还是into
(source code)等函数。