宏、Clojure与Common Lisp比较

18

我和几个朋友正在开发一个新平台,我们希望用lisp语言进行构建。其中最主要的吸引点是宏(macros)功能。我们都使用Common Lisp,但我想尝试Clojure。

当我提出这个想法时,他们中的一个人说Clojure的宏系统“更弱”。我想知道这是不是真的,并在哪些领域上表现较弱。


10
我不确定如何回应“弱”这个词,这取决于你的观点。一件事物是否比另一件事物更弱,取决于你需要什么或者在意什么。也许你可以先询问你的朋友,了解他为什么认为 Clojure 的宏系统比 Common Lisp 的更弱,并且这些问题对他来说为什么很重要。之后,你可以思考一下你自己的观点是否与你的朋友相符。此外,一旦你掌握了具体情况,你可以在 StackOverflow 上提出具体问题,希望获得具体的答案。 - user100464
5个回答

31

他们在功能上基本相当,即:

  • 它们在编译时执行
  • 它们可以执行任意的代码转换和生成(利用 Lisp 代码的homoiconicity
  • 它们适合用于使用新语言结构或 DSL 扩展语言
  • 你会感到非常强大,并且可以非常高效地工作(以beating the averages的方式)

现在来看一些区别:

Common Lisp还允许使用读取宏,这使您可以更改读取器的行为。这允许您引入全新的语法(例如用于数据结构字面量)。这可能是您的朋友将Clojure的宏系统描述为“较弱”的原因,因为Clojure不允许读取宏。在Clojure中,您基本上只能使用(macro-name ....)这种语法,但除此之外,您可以做任何想做的事情。对于读取宏是否好的看法不一:我的个人观点是不好,因为它不会给您带来任何额外的“力量”,并且有可能导致极度混乱。
Clojure在我的看法中,有一个更好的命名空间实现,我认为这使得Clojure的宏系统更容易使用。在Clojure中,每个符号都有命名空间限定,因此不同的库可以在自己的命名空间中定义相同的符号而没有任何冲突的风险。所以,+可以分别定义为clojure.core/+my.vector.library/+,在你自己的命名空间中,你可以use另一个命名空间的定义,这意味着你可以根据需要从clojure.coremy.vector.library中选择+
另一方面,Clojure对于映射{}和向量[]有额外的字面量。这给你比传统的Lisp表达式更多的表达能力(在简洁易读的语法方面)。特别地,用[]来绑定形式是Clojure中的惯例,我认为这对于宏和普通代码都很有效——它让它们与其他括号清晰地区分开来。
Clojure也是一种Lisp-1(像Scheme),因此它没有用于函数和数据的单独命名空间。Common Lisp是一种Lisp-2,它有单独的函数和数据命名空间(因此您可以同时拥有名为foo的函数和数据项)。我稍微更喜欢Lisp-1方法,因为它更简单,并且在编写功能语言时,代码和数据之间的分割似乎有点武断。虽然这可能是个人口味问题。

总体而言,差异相对较小。我认为Clojure更简单、更优雅,而Common Lisp具有一些额外的功能(自行决定使用风险!)。两者都非常强大,因此选择任何一个都不会错。


9
在CL中,向量的写法为#(a b c)。其他数据语法可以通过读取宏来实现。这就是它们的作用,CL保留像{}这样的字符供用户使用。符号的命名空间在CL中被称为包。CL是一种Lisp-n,因为它具有超过2个命名空间。 - Rainer Joswig
5
@Rainer:我认为Lisp-1/Lisp-2通常指的是函数和数据具有独立的命名空间,而不是总命名空间数量。来源:http://www.nhplace.com/kent/Papers/Technical-Issues.html - mikera
9
clojure.core/+my.vector.library/+ 相当于 cl:+my.vector.library:+。在 Common Lisp 中,您可以使用 import-fromshadow 从任何包中导入和隐藏符号,包括 cl - Vsevolod Dyomkin
7
思考:如果“为地图{}和向量[]提供额外的文字”实际上比传统的Lisp s表达式“给你更多表达能力”,并且这些文字可以在通用Lisp中通过阅读器宏创建(我想象他们可以),那么我认为这意味着阅读器宏确实提供了更多“功率”-这似乎与您的说法相矛盾,即阅读器宏没有“给您任何额外的‘功率’”。也许是一种需要谨慎使用的权力,但是这是一个重要的需要注意的权力吧? - lindes
4
能够像 [1 2 3 4] 这样声明向量的语言在功能上与声明为 (vector 1 2 3 4) 或者 #{1,2,3,4}# 的语言完全相等。你可能会认为其中一种比其他方式更加“表达性”,但这是主观的。另一方面,如果你不能在没有显式循环的情况下填充向量,那么从客观意义上讲,你的语言就缺乏了能力——它根本没有提供声明向量字面量的结构。这两件事情是根本不同的。我坚信保罗·格雷厄姆谈到的是后者意义上的能力,而不是化妆性的语法选择。 - mikera
显示剩余7条评论

13

关于普通宏,Lisp和Clojure的变体之间有以下不同:

  1. Clojure是Lisp-1,而CL是Lisp-2。历史已经证明,一个命名空间语言中的宏通常更容易出错,因为存在更大的名称冲突机会。为了缓解这个问题,Clojure使用了一种非传统的准引号实现。
  2. 但代价是:由于在Clojure中,反引号内的符号会被急切地解析为定义宏的名称空间中,所以存在许多微妙的问题,比如这个
  3. Clojure宏使用自动生成符号(auto-gensyms)。这被宣传为一个优点,并且确实删除了一些样板文件(当然,你可以在Lisp中实现相同的效果 - 参见defmacro!)。而何时使用gensym的概念性问题显然仍然存在。

因此,总体而言,Lisp的方法更加粗糙,但更加灵活。Clojure的宏可能稍微更容易上手,但当你超越简单的语法修改并开始定义完整的DSL时,使用起来会更加困难。

关于阅读器宏,如您所知,Clojure不向用户提供此选项,而CL则提供。因此,在CL中,阅读器宏发现了许多用途,例如字符串插值国际化支持SQL DSLJava交互。更不用说大量特殊情况,当阅读器宏可以用于帮助创建高级DSL时。


4
我是80年代在lisp机器上的lisp程序员。就生产力而言,我仍然没有看到一个可比的开发环境。话虽如此,clojure宏系统已经足够强大,已经被滥用了。当你必须实现一个新语言的“编译器”并且你应该能够用正式符号描述它时,宏很有用。不是因为你绝对需要它,而是因为新语言的用户需要它。所以,应该是一个非常有用的正式语言,值得一个编译器和正式符号。如果不是这种情况,远离宏并使用纯函数式编程风格来制作抽象。你会再次感受到使用类比思维进行顺序编码的乐趣,而不是更接近愚蠢的位序列。你的代码用户也会更加快乐。我希望clojure宏的滥用不会成为下一个意外的复杂性。一个老的lisper和clojure的新手。

1

Clojure宏有一些优点:

  • 意外符号捕获很难,因为它扩展命名空间的符号
  • 其他人的读取器宏不会咬你(当然不是你自己,我在说那个人 ;))
  • autogensyms可以大量减少编写卫生宏时的杂乱代码。

自动生成符号似乎有助于提高可读性,尽管您始终可以自行创建。如果要使用变量捕获,您仍然可以这样做吗? - Ferdy
@Frawr 如果你想要变量捕获,你仍然可以使用它,但有一个问题:你必须写 ~'it 来捕获未经限定的符号 it。所以 Paul Graham 的 aif 是(defmacro aif [test true-fm & [false-fm]] \(let [~'it] ~true-fm ~false-fm))` - Riley

1

也许你的朋友指的是Clojure宏系统在某种意义上比较“弱”,因为Clojure目前不支持“读取器宏”,但这并不意味着宏系统更弱,因为据我所知,读取器宏可能会使事情变得非常复杂。

我也不建议你将宏作为主要吸引点。它们很强大,但当你被某种特定技术所吸引时,即使有更简单的技术可用(在宏的情况下,更简单的技术是函数),你也会开始到处应用它。

另一个你应该考虑的重要事情是你想要针对的“平台”,因为Clojure支持JVM,并且还有所有适用于JVM平台的工具。


我不确定JVM能否作为参数,因为[ABCL](http://common-lisp.net/project/armedbear/)的存在。 - Daimrod
3
我认为Clojure与JVM的互操作性要好得多。 - Ankur
谢谢你的警告,我同意(如果这就是你的意思)当函数足够时,通常使用函数是正确的方法。 - Ferdy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接