首页 > 解决方案 > Clojure 中前向声明的限制是什么?为什么我不能在这个例子中使用 comp?

问题描述

我喜欢我的代码具有“自上而下”的结构,这意味着我想做与 Clojure 中的自然相反的事情:在使用函数之前定义函数。不过,这应该不是问题,因为理论上declare我可以先完成所有功能,然后继续享受生活。但在实践中似乎declare无法解决每一个问题,我想了解以下代码不起作用的确切原因。

我有两个函数,我想通过组合这两个函数来定义第三个函数。以下三段代码实现了这一点:

1

(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(defn mycomp [x] (f (g x)))
(println (mycomp 10))

2

(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(def mycomp (comp f g))

3

(declare f g)
(defn mycomp [x] (f (g x)))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))

但我真正想写的是

(declare f g)
(def mycomp (comp f g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))

这给了我

Exception in thread "main" java.lang.IllegalStateException: Attempting to call unbound fn: #'user/g,

这意味着前向声明适用于许多情况,但在某些情况下,我不能只declare使用我的所有函数并以我喜欢的任何方式和任何顺序编写代码。这个错误的原因是什么?前向声明真正允许我做什么,在什么情况下我必须已经定义了函数,例如comp在这种情况下使用?我如何判断该定义何时是严格必要的?

标签: clojureforward-declarationdeclare

解决方案


如果您利用 Clojure 的(记录不充分的)var行为,您可以实现您的目标:

(declare f g)
(def mycomp (comp #'f #'g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))

(mycomp 10) => 45

请注意,语法#'f只是简写(技术上是“阅读器宏”),可转换为(var f). 所以你可以直接写这个:

(def mycomp (comp (var f) (var g)))

并得到相同的结果。

参阅此答案以获取有关 Clojure 符号(例如f, 和符号指向的(匿名)Clojure var 之间的(主要是隐藏的)交互的更详细答案),即#'for 或(var f)。var 反过来又指向一个值(例如你的 function (fn [x] (* x 3)).

当您编写类似 的表达式时(f 10),有两步间接在起作用。首先,符号f被“求值”以找到关联的 var,然后 var 被“求值”以找到关联的函数。大多数 Clojure 用户并没有真正意识到这个两步过程的存在,而且几乎所有时候我们都可以假装在符号f和函数值之间存在直接联系(fn [x] (* x 3))

您的原始代码不起作用的具体原因是

(declare f g)

创建 2 个“空”变量。就像(def x)在符号和空变量之间创建关联一样x,这就是您declare所做的。因此,当comp函数试图从和中提取时,什么都不存在:变量存在但它们是空的。fg


附言

上述情况有一个例外。如果您有let表格或类似表格,则不var涉及:

(let [x 5
      y (* 2 x) ]
  y)  

;=> 10

let表单中,不存在 var。相反,编译器在符号与其关联值之间建立直接联系;即x => 5y => 10


推荐阅读