Clojure中前向声明的限制是什么?为什么我不能在这个例子中使用comp?

div*_*ero 5 clojure declare forward-declaration

我喜欢我的代码有一个"自上而下"的结构,这意味着我想要与Clojure中的自然完全相反:函数在使用之前被定义.这应该不是问题,因为理论上declare我理论上我的所有功能都是第一个,然后继续享受生活.但它似乎在实践declare中无法解决每一个问题,我想了解以下代码无法正常工作的原因.

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

1

(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(defn mycomp [x] (f (g x)))
(println (mycomp 10))
Run Code Online (Sandbox Code Playgroud)

2

(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(def mycomp (comp f g))
Run Code Online (Sandbox Code Playgroud)

3

(declare f g)
(defn mycomp [x] (f (g x)))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
Run Code Online (Sandbox Code Playgroud)

但我真正想写的是

(declare f g)
(def mycomp (comp f g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
Run Code Online (Sandbox Code Playgroud)

这给了我

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

这意味着在许多情况下向前声明工作,但仍有一些情况我不能只是declare我的所有函数,并以任何方式和我喜欢的任何顺序编写代码.这个错误的原因是什么?前向声明真正允许我做什么,以及我必须具有已定义的功能的情况是什么,例如comp在这种情况下使用?如何确定定义何时严格必要?

Ala*_*son 6

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

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

(mycomp 10) => 45
Run Code Online (Sandbox Code Playgroud)

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

(def mycomp (comp (var f) (var g)))
Run Code Online (Sandbox Code Playgroud)

并得到相同的结果.

查看此答案,以获得更详细的答案,了解Clojure符号(如符号所指的Clojure变量)之间的(主要是隐藏的)交互f,即#'f或者(var f).然后,var会指向一个值(例如您的函数)(fn [x] (* x 3)).

当你编写一个表达式时(f 10),工作中有两步间接.首先,对符号f进行"评估"以找到相关的var,然后对var进行"评估"以找到相关的函数.大多数Clojure用户并不是真的意识到这个两步过程存在,几乎所有的时间我们都可以假装符号f和函数值之间存在直接连接(fn [x] (* x 3)).

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

(declare f g)
Run Code Online (Sandbox Code Playgroud)

创建2个"空"变量.就像(def x)在符号x和空变量之间创建关联一样,这就是你declare所做的.因此,当comp函数尝试从和中提取时,不存在任何内容:vars存在但它们是空的.fg


PS

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

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

;=> 10
Run Code Online (Sandbox Code Playgroud)

let表单中,没有var存在.相反,编译器在符号及其关联值之间建立直接连接; 即x => 5y => 10.