为什么不起作用:“定义的第一个论点必须是一个符号”

时间:2018-05-01 10:55:24

标签: 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 f [] 1)

我知道这是深奥的:但我刚想到我可能想在某个时刻动态命名一个函数。 (这里没有真正的用例 - 只是想了解Clojure的想法......)

4 个答案:

答案 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的功能是从语言的更基本部分构建的,无论是whensource code)等宏还是intosource code)等函数。