【问题标题】:How exactly does Clojure process function definitions?Clojure 究竟如何处理函数定义?
【发布时间】:2020-07-02 20:50:31
【问题描述】:

我正在研究 Clojure,并且我读到在 Clojure 中,函数定义只是数据,即参数向量只是一个普通向量。如果是这样,我为什么要这样做

(def add (fn [a b]
  (+ a b)))

但不是这个

(def vector-of-symbols [a b])

?

我知道我通常必须像这样转义符号:

(def vector-of-symbols [`a `b])

但我为什么不用fn/defn 做呢?我认为这是因为 fn/defn 是宏。我尝试检查它们的来源,但到目前为止它们对我来说太先进了。我重新创建defn 的尝试也失败了,我不知道为什么(我从教程中举了一个例子):

(defmacro defn2 [name param & body] 
  `(def ~name (fn ~param ~@body)))

(defn2 add [a b] (+ a b)) ;;I get "Use of undeclared Var app.core/defn2"

谁能解释一下,Clojure 究竟是如何将数据结构,尤其是符号,转化为代码的?我对宏示例缺少什么?

更新 显然,宏不起作用,因为我的项目实际上是在 Clojurescript 中(在 Clojure 中它确实有效)。我认为这并不重要,但随着我的进步 - 我发现越来越多的东西在 Clojurescript 中对我不起作用。

更新 2 这有帮助:https://www.clojurescript.org/about/differences

【问题讨论】:

  • 你的 defn2 宏对我来说很好用。确保在定义它的同一个命名空间中调用它,或使用完全限定名称。

标签: function clojure binding symbols lexical-scope


【解决方案1】:

函数是 Clojure 中的其他数据的一等公民。

要定义一个向量,您使用(vector ...) 或阅读器有语法糖[...],对于列表,它是(list ...)'(...) 引用不将列表评估为函数调用,对于集合(set ...)#{...}

所以一个函数的工厂函数是fn(实际上fn*,来自Clojure的Java核心,fn是一系列用于解构的宏)。

(fn args 正文)

是返回函数的函数调用,其中 args 是参数事件的向量。 empty 和 body 是一系列 Clojure 表达式,通过 args 绑定到环境进行评估。如果没有要评估的内容,则返回 nil。还有一个语法糖#(...)%x 作为参数 x,% 作为参数 1。

(fn ...) 返回一个函数值。所以

(def my-super-function (fn [a b c d] (println "coucou") (+ a b c d)))

将符号my-super-function(fn [a b c d] (println "coucou") (+ a b c d))返回的匿名函数绑定。

(def my_vector [1 2 3])

将符号my_vector与向量[1 2 3]绑定

【讨论】:

  • "fn*,来自 Clojure 的 Java 核心" - 从您和 A. Thompsons 的回答中,我知道编译器以特殊方式处理 fn*,尤其是参数向量?或者他们是否在途中某处逃逸到原始符号中? IE。 ,当我们直接使用 fn* 时,(def add4 (fn* [f g] (+ f g))) ,为什么 [f g](+ f g) 不像在正常函数调用期间那样评估?
  • 我猜,这回答了我的问题,fn 是一种特殊形式? clojure.org/reference/special_forms
【解决方案2】:

学习资源列表:https://github.com/io-tupelo/clj-template#documentation

正如@jas 所说,您的 defn2 宏看起来不错。

重点是宏是一项几乎不需要的高级功能。宏相当于编译器扩展,而这几乎不是解决问题的最佳方案。还要记住,函数可以做一些宏不能做的事情。

另一点:syntax-quote(又名反引号)` 与单引号非常不同。在您的示例中,您需要 ['a 'b] 的单引号。更好的是引用整个向量形式'[a b]

至于您的主要问题,源文件文本如何转换为代码的解释很差。这是一个两步的过程。 Clojure Reader 使用文本字符串数据(来自文件或文字字符串)并生成数据结构,如列表、向量、字符串、数字、符号。 Clojure 编译器将这些数据结构作为输入,并生成可执行的 java 字节码。

这是令人困惑的,因为在打印时,无法区分向量 [1 2 3] 的文本表示和输入给阅读器 [1 2 3] 的文本字符串之间的区别。理想情况下,它将是彩色编码或其他东西。这个问题在 Java 等中不存在,因为它们没有宏,因此源代码(文本)和宏使用的数据结构(不是文本)之间没有混淆。

有关在 Clojure 中创建宏的更详细答案,请see this answer

【讨论】:

  • "这是一个两步过程。Clojure Reader ... Clojure 编译器 ..." 我是否理解正确: 1. Reader 仅解析符号,但实际上并未在语义上绑定它们; 2、fn宏最终产生了内部特殊函数fn*的定义,这是Compiler专门处理的,而Compiler在这种情况下使用符号的特殊语义解释?
  • 澄清一下,我试图理解这一点:当我们直接使用 fn* 时,`(def add4 (fn* [f g] (+ f g))) ,为什么不 [ f g] 和 (+ f g) 在正常函数调用期间进行评估?
  • 我猜,这回答了我的问题,fn 是一种特殊形式? clojure.org/reference/special_forms
猜你喜欢
  • 2021-11-23
  • 1970-01-01
  • 2018-01-04
  • 1970-01-01
  • 2018-09-17
  • 1970-01-01
  • 1970-01-01
  • 2016-02-17
  • 1970-01-01
相关资源
最近更新 更多