宏,clojure与普通lisp

Fra*_*awr 16 macros clojure common-lisp

我和我的一些朋友正在开发一个新平台,我们希望在lisp中构建它.主要的吸引力是宏.我们都使用Common Lisp,但我想探索Clojure的选项.

当我提出这个时,其中一个人说宏观系统"较弱".我想知道这是否属实,以及在哪些方面.

mik*_*era 26

就你可以用它们做什么而言,它们都非常相同,即:

  • 它们在编译时执行
  • 他们可以执行任意转换和代码生成(利用Lisp代码的同音性)
  • 它们适合用新语言结构或DSL"扩展语言"
  • 你感觉非常强大,并且可以非常高效(以平均的方式击败)

现在有一些差异:

Common Lisp还允许读取器宏允许您更改读取器的行为.这允许您引入全新的语法(例如,用于数据结构文字).这可能是你的朋友将Clojure的宏系统描述为"较弱"的原因,因为Clojure不允许读者宏.在Clojure中,你基本上坚持语法,(macro-name ....)但除此之外,你可以做任何你想做的事情.关于读者宏是否是好事的意见分歧:我的个人观点不是,因为它没有给你任何额外的"力量",并且有可能引起极度混乱.

在我看来,Clojure有一个更好的命名空间实现,我认为Clojure的宏系统更容易使用.Clojure中的每个符号都是名称空间限定的,因此不同的库可以在它们自己的名称空间中定义不同的相同符号.因此+可以单独定义为clojure.core/+ and my.vector.library/+没有任何冲突风险.在您自己的命名空间中,您可以使用另一个命名空间中的定义,这意味着您可以选择+从任一个clojure.coremy.vector.library根据需要获取.

另一方面,Clojure 为地图和向量提供了额外的文字.与传统的Lisp s表达式相比,它们使您的表达能力更强(在简洁的可读语法意义上).特别是,对于绑定表单使用[]是Clojure中的一种惯例,我认为它对宏和普通代码都很有效 - 它使它们从其他括号中清楚地脱颖而出.{}[]

Clojure也是一个Lisp-1(类似于Scheme),因此它没有单独的函数和数据命名空间.Common Lisp是一个Lisp-2,它有独立的函数和数据名称(因此你可以同时拥有一个名为foo的函数和一个名为foo的数据项).我稍微喜欢Lisp-1方法,因为它更简单,当你在函数语言中编写时,代码和数据之间的划分似乎有点武断.这可能是个人品味的事情.

总体而言,差异相对较小.我认为Clojure更简单,更优雅,而Common Lisp有一些额外的功能(使用风险自负!).两者都非常有能力,所以你也不能选错.

  • CL中的向量写为#(abc).其他数据语法可以通过读取宏来实现.这就是它们的用途,CL为用户保留了像{}这样的字符.符号的命名空间在CL中称为包.CL是一个Lisp-n,因为它有2个以上的命名空间. (9认同)
  • `clojure.core/+`和`my.vector.library/+`相当于`cl:+`和`my.vector.library:+`.在CL中,您可以从任何包中"import-from"和"shadow"符号,包括`cl` (8认同)
  • 思考:如果"map {}和vectors []"的额外文字实际上"比传统的Lisp s表达式更具表现力",那么这些文字可以用读者宏在普通的lisp中创建(我想他们然后我认为这意味着读者宏*做*提供更多的"力量" - 在我看来,与你的说法相反,读者宏不会"给你任何额外的"力量"".或许,谨慎使用的权力,但重要的是要注意,不是吗? (6认同)
  • @Rainer:我认为Lisp-1/Lisp-2通常是指函数和数据具有单独的命名空间,而不是命名空间的总数.资料来源:http://www.nhplace.com/kent/Papers/Technical-Issues.html (5认同)
  • 你可以将一个向量声明为"[1 2 3 4]"的语言与你将其声明为`(向量1 2 3 4)`或甚至是`#{1,2,3,4 }#`.你可能会争辩说,一个人比其他人更"表达",但这是主观的.OTOH,如果你不能在没有显式循环的情况下填充向量,那么你的语言在客观意义上缺乏力量 - 它根本不提供用于声明向量文字的构造.这两件事情根本不同.我仍然相信保罗格雷厄姆在后一种意义上谈论权力,而不是关于化妆品句法选择. (3认同)
  • 我的2美分 - 引自Paul Graham的"On Lisp",第225页:"[......]读取宏至少和普通的宏一样强大.事实上,读取宏是**更强大**[强调我的至少有两种方式:读取宏会影响Lisp读取的所有内容,而宏只会在代码中扩展.由于读取宏通常会递归地调用"读取",因此像"a"这样的表达式会变成`(引用(引用a))`,而如果我们试图使用普通宏来定义`quote`的缩写,那么它将独立工作,但不能在嵌套时工作." (2认同)

Vse*_*kin 12

关于普通宏,这里是Lisp和Clojure变体之间的区别:

  1. Clojure是Lisp-1,而CL是Lisp-2.历史表明,一个命名空间语言中的宏通常更容易出错,因为有更大的名称冲突机会.为了缓解这个问题,Clojure使用了非传统的准报价实现.
  2. 但它需要付出代价:因为在Clojure中,反引号内的符号在命名空间中被急切地解析,其中宏被定义,存在许多微妙的问题,例如这个.
  3. Clojure宏使用自动gensyms.这被宣传为一个优势,它确实删除了一些样板(当然,你可以在Lisp中实现相同 - 参见defmacro!).显然,关于何时使用gensyms的概念性问题仍然存在.

总的来说,Lisp的方法更粗糙,但更灵活.当你超越简单的语法修改并开始定义完整的DSL时,Clojure的宏可能会稍微容易接近,但会变得更难使用.

关于读者宏,正如你所知,Clojure没有给用户这个选项,而CL也是如此.所以在CL阅读器中,宏找到了很多用途,比如字符串插值,国际化支持,SQL DSLjava互操作.更不用说过多的特殊情况,当读者宏可用于帮助创建高级DSL时.