Clojure和F#有哪些DSL创建工具?

7
我正在尝试确定Clojure和F#用于创建DSL的设施。每个语言都提供了哪些设施来创建和操作DSL?
由于F#是静态类型的,这会使得这个特定任务更加困难吗?在Clojure方面,我没有真正的经验,但所有LISP都以元编程/DSL而闻名。
我的问题并不是为了在两种语言之间进行战争或类似的事情。如果我最近对两者进行了提问,那是因为我确实认为两者都很棒,并想更多地了解两者的具体情况。
在阅读了几天关于意图编程的内容后,它让我重新激起了对DSL和其他一切的兴趣。
虽然我对F#有一些了解,但我还没有使用引用或类似东西开发过任何东西。我看到了基于辨别联合的DSL示例,这似乎很有趣。

这是一个开放性且相当主观的问题。请阅读常见问题解答。 - Onorio Catenacci
2个回答

5
您可以使用任何语言最终创建DSL。
Clojure /其他Lisps的独特之处在于它们是homoiconic,这使得它们非常适合元编程 - 也就是说,该语言本身自然地表达为相同语言的数据结构。在Lisp中,您实际上是将代码直接编写为一个AST
这是非常强大的 - 这意味着代码生成等效于创建一个相对简单的数据结构。而且,该语言提供了利用宏在编译时生成任意代码的功能。这有效地允许您“扩展语言”以支持您需要的任何特定DSL。
例如,我最近发现自己想要Clojure中的命令式for循环(向函数式编程纯粹主义者道歉,但有时确实需要)。将其添加到语言中只需五行代码:
(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)] (recur ~change new-value#))
       value#)))

现在我可以做:

(for-loop [i 0 (< i 10) (inc i)]
   (println i))
=> < prints numbers from 0..9 >

这显然是一个简单的例子,但是希望能清楚地表明,通过创建一组短宏扩展到你想要的确切代码,可以产生新的语言结构的能力使得构建DSL变得特别容易。
一些你可能会感兴趣的阅读/链接:
- 好奇的Clojure程序员 - 优秀的视频,描述了Clojure的一些独特功能(并与Java进行了一些有趣的比较) - Paul Graham - 打败平均值 - Lisp宏的特殊之处

非常感谢您的评论。我毫不怀疑稍后会阅读所有这些内容(格雷厄姆的我已经在一段时间前读过了;它是一个很好的经典)。亚历克斯提出的一个观点我忘了提到...我假设这都是为了制作嵌入式DSL,而为了制作外部/独立的DSL,有标准的词法分析和语法分析的方式。我是对的吗? - Jacobo Polavieja
2
Lisp非常适合嵌入式DSL,因为正如mikera在上面的答案中提到的那样,Lisp程序是通过使用列表来表示的,您可以随意操纵它们。如果您想编写自定义词法分析器/解析器,则必须使用一些Lisp解析器生成器或手动编写自己的解析器。 - Alex
1
@Jacobo - 嗯,编写一个生成Lisp AST的解析器然后继续以正常方式进行是相当容易的。如果你已经在使用Clojure,我怀疑是否值得引入外部解析器生成器,比如lex / yacc:已经有一些解析器库可以完成这个任务(例如那些受Haskell的单子解析器组合器启发的库)。 - mikera
仅限于Lisp嵌入式领域特定语言,翻译为“非常适合元编程”。 - J D
@Jon Harrop,我一直喜欢您在这些话题上的评论和文章,也非常感谢您关于 F# 的文章。您能否进一步阐述一下?您是在暗示两种语言都同样适合外部 DSL 吗?您是在暗示 Clojure 只适用于与某些特征匹配的内部 DSL 吗?也许您同时在暗示这两个问题? ;) - Jacobo Polavieja
1
@JacoboPolavieja 谢谢!这个问题有很多值得探讨的地方,我在写这篇文章时一直忍着不说。Homoiconicity 完全没有用处,甚至可能比有用还要更加有害(参见 Mathematica 和 OCaml)。Lisp 风格的宏有其用途,但是除非你的 DSL 是一个嵌入式 Lisp 方言,否则它们对 DSL 的实现帮助不大。我并不是说 Clojure 会比 F# 更差,只是这里声称的优势是虚假的。比较一下现有的解决方案,例如用 Clojure 和 F# 编写的正则表达式编译器,会很有趣... - J D

4
我不能谈论Clojure,因为我没有使用过它,但是我了解一些关于F#中DSL的东西。F#提供了两个主要的语言编程特性(Don Syme喜欢称之为):代码引用和计算表达式。
代码引用更接近于Lisp等语言中的宏。它们允许您以程序方式生成表达式,然后执行这些表达式。通过在F#表达式上使用ReflectedDefinition属性,您可以访问它们的ASTs。有关详细信息,请参见http://msdn.microsoft.com/en-us/library/dd233212.aspx
计算表达式类似于Haskell中的do符号。编译器使用特殊语法重写代码,使其成为对您定义的类型的调用。这种类型应该理想地形成一个单子。由于它们是伪装的monad,因此它们应该允许您实现DSL的自定义语义。有关详细信息,请参见http://msdn.microsoft.com/en-us/library/dd233182.aspx
在我看来,计算表达式更适合在F#之上编写DSL,而代码引用更适合像转换或翻译(例如F#到JavaScript)这样的任务。
除了这两个主要特性,您还可以使用语言的其余部分。
当然,上面我只谈到了嵌入式特定领域语言。您可以更进一步,使用fslex和fsyacc来创建一个独立的DSL。

感谢您对这两种技术的评论和见解。似乎在任何F#或Clojure擅长的领域,您都会发现另一种语言同样擅长。关于嵌入式或外部DSL的好处,您提出了一个很好的观点,这是我忘记提到的。通过研究每种语言的特点和处理事情的方式,我确实学到了很多。再次感谢! - Jacobo Polavieja

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