在查看Clojure代码时,如何识别哪些窗体是宏,哪些窗体是函数?

在查看Clojure代码时,如何识别哪些窗体是宏,哪些窗体是函数?,clojure,clojurescript,Clojure,Clojurescript,Lisp/Clojure代码在语法上具有一致性,这是一个优点,因为不需要理解各种不同的构造。 但有时,在不阅读文本的情况下,通过使用不同的语法查看一段代码更容易理解,例如,这是一个switch case或这是模式匹配构造等 几个月前我开始使用Clojure,我意识到如果不阅读表单的名称,然后用谷歌搜索它是宏还是函数,以及它是如何工作的,我就无法理解代码 所以事实证明,一段Clojure代码,不管语法是否一致,都是不一致的 它可能看起来像一个函数,但如果它是一个宏,那么它可能不会计算它的所有参数

Lisp/Clojure代码在语法上具有一致性,这是一个优点,因为不需要理解各种不同的构造。 但有时,在不阅读文本的情况下,通过使用不同的语法查看一段代码更容易理解,例如,这是一个switch case或这是模式匹配构造等

几个月前我开始使用Clojure,我意识到如果不阅读表单的名称,然后用谷歌搜索它是宏还是函数,以及它是如何工作的,我就无法理解代码

所以事实证明,一段Clojure代码,不管语法是否一致,都是不一致的

它可能看起来像一个函数,但如果它是一个宏,那么它可能不会计算它的所有参数


是否有所有宏都使用的命名约定或缩进样式,以便人们更容易通过名称了解正在发生的事情?

据我所知,没有强制的命名约定

根据经验,函数在任何可能的情况下都是首选的,但是当宏遵循设置某物的模式
def
,或使用-使用开放资源执行某物时,有时可以发现宏


因此,您可能会发现clojure的
doc
宏非常有用。它将告诉您表单是否是宏/函数/特殊表单,并给出其参数列表和文档字符串(如果存在)。比如说

(use 'clojure.repl)
(doc and)
将以下内容打印到repl

clojure.core/and

([[x][x和下一步])

从左到右一次计算一个表达式。如果是表格 返回逻辑false(nil或false),并返回该值和 不计算任何其他表达式,否则返回 最后一个表达式的值。(and)返回true


一些编辑器(如emacs)将以弹出式或组合键的形式提供此文档,从而使访问(和阅读)成为可能更快。

在我看来,最有用的直觉来自于理解给定运算符/Var的用途。设计良好的宏不能作为函数来编写,而仍然提供相同语法的相同功能,因为如果可以,它们实际上将作为函数来编写(请参阅上面的“精心设计”部分!).1所以,如果你正在处理一个不可能是正则函数的构造,那么你知道它不是;否则很可能是这样

此外,了解库导出的变量的常用方法会告诉您是预先处理宏还是函数。这对于
doc
(doc foo)
foo
是一个接近其输出顶部的宏,如果确实如此),
source
(因为它提供了整个代码)和M-。(使用nrepl.el或swank clojure跳转到Emacs中的定义;M-,跳回)。文档可能会提到什么是宏,什么不是宏(除了docstring不一定是这样,因为所有访问docstring的常用方法都已经告诉您是否正在处理宏,如上所述)

如果你浏览一段代码,目的是在假设不同的操作符执行其名称所建议的功能的基础上,对代码的功能有一个大致的了解,那么要么(1)这些名称具有足够的暗示性,并且你对代码的意图有了一个概念,因此,您甚至不需要关心哪些操作符恰好是宏,或者(2)名称不够具有启发性,因此您需要深入了解一些操作符的文档或源代码,然后您将了解到的第一件事是哪些操作符注册为宏

最后,宏没有单一的命名样式,尽管有特定于特定用例的特定约定。例如,使用foo的
——风格的构造往往是方便的宏,其目的是简化对
foo
类型资源的处理
dofoo
-风格的构造往往是宏,它接受要执行的表达式体(多少次以及使用哪些附加上下文设置取决于宏;这个家族中最基本的成员,
do
,实际上是一种特殊形式,而不是宏)
deffoo
-样式构造引入了新的变量或类似类型的实体

值得指出的是,类似的模式有时会被打破。例如,大多数线程构造(
->
&Co.)都是宏,但是
clojure.data.zip.xml
中的
xml->
是一个函数。当我们考虑到所提供的功能时,这是非常有意义的,这让我们回到了操作员是直觉最有用来源的目的



1此规则可能有一些例外情况。人们会期望这些被记录下来。当然,有些项目根本没有文档记录(或者几乎没有文档记录);在这里,问题完全消失了,因为无论如何,我们都必须找到源代码来理解事物。

有两个属性通常区分宏(有时是特殊形式)和函数:

  • 当表单进行某种绑定时(即声明新标识符以供以后使用)
  • 当某些参数被惰性地计算时
  • 第一种情况的示例是
    let
    letfn
    绑定
    与本地变量
    。奇怪的是,
    defn
    被定义为一个函数,但我很确定它与Clojure的引导过程有关(
    defn
    是在定义
    defmacro
    之前定义的)

    第二个示例是
    惰性序列。在所有这些构造中,参数都是通过将它们放在cond中进行惰性计算的