【发布时间】:2023-04-04 22:31:01
【问题描述】:
我想使用宏在 Scala 中实现外部 DSL,例如 SQL。我已经看过有关如何实现internal DSLs with Scala 的论文。另外,我最近写了一篇关于how this can be done in Java的文章。
现在,内部 DSL 总是让人觉得有点笨拙,因为它们必须在宿主语言(例如 Scala)中实现和使用,并遵守宿主语言的语法约束。这就是为什么我希望 Scala 宏将允许在没有任何此类限制的情况下内部化外部 DSL。但是,我并不完全了解 Scala 宏以及我能用它们走多远。我已经看到SLICK 和一个鲜为人知的库sqltyped 已经开始使用宏,但是 SLICK 使用“Scalaesque”语法进行查询,这不是真正的 SQL,而 sqltyped 使用宏来解析 SQL字符串(也可以在没有宏的情况下完成)。此外,各种examples given on the Scala website 对于我正在尝试做的事情来说太琐碎了
我的问题是:
假设外部 DSL 被定义为一些 BNF 语法,如下所示:
MyGrammar ::= (
'SOME-KEYWORD' 'OPTION'?
(
( 'CHOICE-1' 'ARG-1'+ )
| ( 'CHOICE-2' 'ARG-2' )
)
)
我可以使用 Scala 宏实现上述语法以允许这样的客户端程序吗?还是 Scala 宏不够强大,无法实现这样的 DSL?
// This function would take a Scala compile-checked argument and produce an AST
// of some sort, that I can further process
def evaluate(args: MyGrammar): MyGrammarEvaluated = ...
// These expressions produce a valid result, as the argument is valid according
// to my grammar
val result1 = evaluate(SOME-KEYWORD CHOICE-1 ARG-1 ARG-1)
val result2 = evaluate(SOME-KEYWORD CHOICE-2 ARG-2)
val result3 = evaluate(SOME-KEYWORD OPTION CHOICE-1 ARG-1 ARG-1)
val result4 = evaluate(SOME-KEYWORD OPTION CHOICE-2 ARG-2)
// These expressions produce a compilation error, as the argument is invalid
// according to my grammar
val result5 = evaluate(SOME-KEYWORD CHOICE-1)
val result6 = evaluate(SOME-KEYWORD CHOICE-2 ARG-2 ARG-2)
注意,我对 solutions that parse strings 不感兴趣,就像 sqltyped 一样
【问题讨论】:
-
目前宏仅限于普通函数调用。也就是说,触发宏扩展的唯一方法是调用 def 宏。此外,def 宏的参数必须是类型良好的 Scala 表达式,这意味着您必须符合 Scala 的语法和类型规则。未来的研究可能会解除后者甚至前者的限制,但如何以及何时发生尚不清楚。
-
@EugeneBurmako:感谢权威反馈!这可以作为一个答案,因为你是 Scala 宏的人:-)
-
@LukasEder:你说字符串解析方法“可以在没有宏的情况下完成”,但这只是对了一半——不要在这里低估编译时安全的价值。
-
@TravisBrown:是的,我知道使用宏进行字符串解析有好处。但是,这些字符串将需要是(内联)字符串文字,这让我觉得字符串解析方法是真正内部和真正外部 DSL 之间的“杂物”......除非我错过了什么?
-
不,你是对的——作为一个通用的 DSL 解决方案,它很笨拙,但在许多情况下(特别是在使用字符串文字已经很普遍的情况下——例如,正则表达式、SQL 查询)能够验证编译时的嵌入式语言是一个巨大的优势。
标签: scala dsl scala-macros