使用Clojure宏来创建领域特定语言(DSL)

11
我正在开发一个Clojure项目,经常编写用于 DSL 的 Clojure 宏。但我看了一段公司如何在实际工作中使用Clojure的视频,演讲者说他们不会在实际应用中使用宏来编写DSL,只使用宏添加少量的语法糖。这是否意味着我应该先使用标准函数编写我的DSL,然后再在最后添加一些宏呢?
更新: 阅读了许多不同(也很有趣)的答案后,我意识到答案不像我最初想的那么简单,原因如下:
1. 应用程序中有许多不同类型的 API(内部、外部)。 2. API 有许多不同类型的用户(希望快速完成任务的业务用户,Clojure 专家)。 3. 宏是否隐藏了样板代码?
我将去深入思考这个问题,但感谢您的回答,它们给了我很多思考的方向。另外,我注意到Paul Graham认为与Christophe视频相反,宏应该是代码库的重要组成部分(25%): http://www.paulgraham.com/avg.html
4个回答

12
在某种程度上,我认为这取决于DSL的使用/目的。如果您正在编写一个类似库的DSL以在Clojure代码中使用并希望以函数方式使用,则我更喜欢函数而不是宏。对于Clojure用户来说,函数非常“好”,因为它们可以动态地组合成更高级别的函数等。例如,您正在编写一个功能性Web框架,如Ring。如果您正在编写一个独立于其他Clojure代码使用的命令式DSL,并且已经确定您绝对不需要高阶函数,则使用方式将非常相似,您可以选择其中最有意义的方式。例如,您可能正在创建某种业务规则引擎。如果您正在编写需要生成高性能代码的专业化DSL,则大多数情况下您可能希望使用宏,因为它们将在编译时扩展以实现最大效率。例如,您正在编写一些需要扩展到正确的OpenGL调用序列的图形代码......

7

是的!

尽可能使用函数编写代码,而不是宏。如果你写了太多宏,最终会导致代码难以扩展。例如,宏不能应用于特定情况或传递给其他地方。

克里斯托弗·格兰德:(不是DSL宏)

http://clojure.blip.tv/file/4522250/


不,我认为我们在谈论不同类型的DSL。我说的是普通API变得更好(该库的迷你DSL),而不是像嵌入式Prolog那样的东西。(我认为大多数人并不经常编写完整的语言实现) - nickik
@nickik,这并不重要——即使您的DSL只是Swing的包装器,它编译和静态验证都比解释(并在运行时失败)要好得多。 - SK-logic
@SK-logic 你需要将静态验证构建到宏中,但大多数人都不想这样做。你只是想要包装模板。你现在的主张是过度工程化。你忽略了宏比函数更难编写,这使得你的代码更难读懂。 - nickik
2
@nickik,Christophe 的主要观点是“编写 DSL 很难(至少对他来说是这样)”。这并不足以成为选择高阶函数而不是 AST 转换的论据——这只是表明 完全走错了路。实际上,Clojure 社区的绝大多数人都在错误的道路上,试图实现复杂的、单片的宏,而不是一系列微不足道的转换链。 - SK-logic
1
@nickik,宏比函数更容易编写。要简单得多。看看我在另一个评论中的例子。如果这对你来说很难,那么你应该改变方法。宏应该是琐碎的。为什么像你们这样的函数式程序员能够将问题分解为小而易懂的函数,但却无法用相同的方式处理树转换器呢?只需从正确的角度开始考虑宏:语言在一系列树转换中被翻译到另一个语言,每个转换都应该做一件简单的事情。然后以任何你喜欢的方式将它们链接在一起。 - SK-logic
显示剩余3条评论

3
不!不要害怕大量使用宏。当你有疑问时,总是编写宏。对于实现DSL来说,函数是次优的选择 - 它们把负担放在运行时上,而宏可以在编译时进行许多重量级计算。只需想象一下,将嵌入式Prolog实现为解释器函数和将Prolog编译为某种WAM形式的宏之间的差异即可。
而且不要听那些声称“宏无法应用或传递”的人的话,这个论点完全是一个稻草人。这些人提倡解释器而不是编译器,这是完全荒谬的。
以下是如何使用宏实现DSL的几个技巧:
- 分阶段进行。从你的DSL到基础Clojure定义一个长链的语言。尽可能保持每个转换的简单性 - 这样您就可以轻松地维护和调试DSL编译器。 - 准备一个DSL组件工具箱,在实现DSL时将其重复使用。它应该包括具有不同语义的目标语言(例如,未类型化的急切功能 - 就是Clojure本身,未类型化的惰性功能,一阶逻辑,类型命令式,Hindley-Millner类型的急切功能,数据流等)。使用宏可以轻松地无缝组合所有目标语义的属性。 - 维护一套编译器构建工具。它应该包括解析器生成器(即使您的DSL完全是S表达式也很有用),术语重写引擎,模式匹配引擎,实现图上一些常见算法的实现(例如,图着色)等。

1
在这种情况下,我的观点并不特别重要,因为整个Clojure社区的普遍观点是,您正在提倡DSL的人们应该追求的相反方向。我不想引发争论或攻击您,但我必须说,我希望那些不知道更好的初学者很快就会发现他们需要忽略您。还有“小懦夫”?冷静点,先生,让我们保持礼貌。 - Rayne
4
@Rayne,一个所谓的“社区”认为面向对象编程(OOP)是软件架构中最优秀的方法。这个“社区”认为你钟爱的函数式编程只是给疯狂的极客和学者用的,而且在企业中完全不可接受。你同意多数人的看法吗?那么为什么在这种情况下,你还要引用这个多数人的不合理、反生产力的观点呢? - SK-logic
1
好吧,那不是我想表达的重点。我想表达的重点是,在必要时使用宏是一种惯例。就像不使用单段命名空间一样。这个惯例的原因已经很清楚了。特别是在克里斯托夫·格兰德的演讲中。我没有什么可以补充的了,而且由于你们几乎在听到这些想法和结论之前就抛弃了它们,我不确定我能否说服你们改变看法。唉,对不起,我很胆怯。 :) - Rayne
1
@Rayne,这正是我反对的观点。Christophe建立他的论点在一个完全错误的基础上,因为宏并不难写。编译器比解释器更容易,有一个非常明显的原因 - 每次编写DSL时都必须实现一个解释器,如果使用宏,则可以重用现有的编译器和所有您已经编写的其他DSL。一个令人信服的论点将是一个基于函数的DSL示例,它比任何可能的基于宏的DSL更容易,更可读且更易维护。我对这种类型的论点持开放态度。 - SK-logic
1
@SK-logic 我认为Grand的演示和SPJ的合同是“基于函数的DSL的例子,比任何可能的基于宏的DSL更容易、更可读和更易于维护。”也许不是任何基于宏的DSL,但是我见过的所有基于宏的DSL都是如此。 - wilkes
1
@wilkes,这不是最容易理解的DSL,但从我所理解的来看,使用宏实现会更容易-整个“eval”部分将被省略。我会尝试记录一个简单的实现。 - SK-logic

2

一旦您拥有了所有这些组件,实现甚至最微不足道的DSL编译器都比任何基于高阶函数的解释器更容易。请参见以下示例:http://www.meta-alternative.net/calc.pdf和http://www.meta-alternative.net/pfront.pdf。 - SK-logic

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