正如Rich Hickey所说,Lisp语言的秘诀在于能够通过宏直接操作抽象语法树。这可以用任何非Lisp方言语言实现吗?
答案 0 :(得分:22)
能够“直接操纵抽象语法树”本身并不是什么新鲜事,尽管这是很少有语言的东西。例如,现在许多语言都有某种eval
函数 - 但很明显,那些不操纵抽象语法树,相反,它是对具体语法的操纵 - 直接源代码。顺便提一下,D中的mentioned functionality与CPP属于同一类别:两者都处理原始源文本。
举一个具有该功能的语言示例(但不是适当的宏),请参阅OCaml。它有一个语法扩展系统CamlP4,它本质上是一个编译器扩展工具包,它围绕OCaml抽象语法作为其最重要的目的。但这仍然不能使Lisps中的相应功能如此出色。
Lisps的重要特性是,使用宏获得的扩展是语言的一部分,与任何其他语法形式相同。换句话说,当你在Lisp中使用类似if
的东西时,无论是作为宏还是作为原始形式实现,功能上都没有区别。 (实际上存在一些细微差别:在某些情况下,了解一组不进一步扩展的原始形式非常重要。)更具体地说,Lisp库可以提供普通绑定和宏,这意味着库可以用比大多数语言中常见的无聊扩展更有趣的方式扩展语言,只能添加普通绑定(函数和值)。
现在,从这个角度来看,D设施之类的东西在性质上非常相似。但它处理原始文本而不是AST的事实限制了它的实用性。如果您查看该页面上的示例,
mixin(GenStruct!("Foo", "bar"));
你可以看到这不是看起来像语言的一部分 - 为了使它更像Lisp,你会以自然的方式使用它:
GenStruct(Foo, bar);
不需要标记使用宏的mixin
关键字,不需要!
,并且标识符被指定为标识符而不是字符串。更好的是,定义应该更自然地表达,例如(在这里发明一些错误的语法):
template expression GenStruct(identifier Name, identifier M1) {
return [[struct $Name$ { int $M1$; }; ]]
}
这里需要注意的一件重要事情是,由于D是一种静态类型语言,因此AST会以明确的方式进入这种心理练习 - 作为identifier
和expression
类型(我是假设template
将此标记为宏定义,但仍需要返回类型。)
在Lisp中,你实际上得到的东西非常接近这个功能,而不是糟糕的字符串解决方案。但是你得到的更多--Lisp故意在基本列表类型上发挥作用,并以一种非常简单的方式将AST与运行时语言统一起来:AST由符号和列表以及其他基本文字(数字,字符串,布尔值)组成,这些都是运行时语言的一部分。事实上,对于那些文字,Lisp向前迈出了一步,并使用文字作为自己的语法 - 例如,数字123
(运行时存在的值)由还数字123
(但现在它是编译时存在的值)。最重要的是,与其他语言称之为“宏”相比,Lisp中与宏相关的代码往往更容易处理。想象一下,例如,使D示例代码在结构中创建 N int
字段(其中N是宏的新输入) - 这将需要使用某个函数来转换字符串成一个数字。
答案 1 :(得分:6)
答案 2 :(得分:5)
为了完整起见,除了已经提到的语言和预处理器之外:
答案 3 :(得分:3)
我不确定你是否称它为“语法抽象”本身,但它肯定可以做很多Lisp可以做的事情:
The mixin
keyword允许您将字符串转换为代码(以比C宏更好的方式),当与模板结合使用时(比C ++中的要好得多),您可以做得很漂亮你想要的任何东西。
答案 4 :(得分:2)
Prolog就是这样一种语言。有很多Prolog方言。一个想法是他们的基本构建块是一个术语(类似于编码函数的s表达式)。有解析器为它提供宏设施。
答案 5 :(得分:1)
我会说Tcl符合条件 - 好吧,这取决于你是否认为Tcl是一个Lisp。
标准分组字符{
}
实际上只是一个字符串文字(没有变量插值),并且有一个eval
,因此您可以轻松定义自己的控制流或循环语法(人们经常这样做。)