我读到很多关于LISP可以通过宏动态地重新定义语法的内容。我很好奇这到底有多远?你能否重新定义语言结构,使其边缘化成为另一种语言的编译器?例如,你能否将LISP的功能性改变为更接近面向对象的语法和语义,比如更接近Ruby的语法?
特别是,使用宏是否可能消除括号混乱?我已经学会了足够的(Emacs-)LISP来定制我的微小功能,但我非常好奇宏在定制语言方面能达到什么程度。
我读到很多关于LISP可以通过宏动态地重新定义语法的内容。我很好奇这到底有多远?你能否重新定义语言结构,使其边缘化成为另一种语言的编译器?例如,你能否将LISP的功能性改变为更接近面向对象的语法和语义,比如更接近Ruby的语法?
特别是,使用宏是否可能消除括号混乱?我已经学会了足够的(Emacs-)LISP来定制我的微小功能,但我非常好奇宏在定制语言方面能达到什么程度。
这是一个非常好的问题。
我认为这个问题很微妙,但绝对可以回答:
宏并不固定在s表达式中。LOOP宏就是用关键字(符号)编写的非常复杂的语言。因此,虽然你可能会在循环的开头和结尾使用括号,但里面有自己的语法。
例如:
(loop for x from 0 below 100
when (even x)
collect x)
话虽如此,大多数简单的宏只使用s表达式。并且你会“被困”在它们中使用。
但是就像Sergio所回答的那样,s表达式开始感觉正确。语法脱离了束缚,你开始在语法树上编码。
至于阅读器宏,是的,你可以构想出这样的代码:
#R{
ruby.code.goes.here
}
但你需要编写自己的Ruby语法解析器。
你还可以使用宏来模仿一些Ruby结构,例如块,这些宏会编译成现有的Lisp结构。
#B(some lisp (code goes here))
会被翻译成
(lambda () (some lisp (code goes here)))
参见此页面了解如何执行。
是的,您可以重新定义语法,使Lisp成为编译器。您可以使用“读取器宏”来实现这一点,这与您可能想到的普通“编译器宏”不同。
Common Lisp具有内置的功能,用于定义阅读器的新语法和处理该语法的阅读器宏。此处理在读取时间完成(在编译或评估时间之前)。要了解有关在Common Lisp中定义阅读器宏的更多信息,请参见Common Lisp Hyperspec--您将需要阅读第2章,“语法”和第23章,“阅读器”。(我相信Scheme也具有相同的功能,但我对它不太熟悉--请参见Scheme sources以获取Arc编程语言)。
作为一个简单的例子,假设您希望Lisp使用花括号而不是圆括号。这需要类似于以下阅读器定义:
;; { and } become list delimiters, along with ( and ).
(set-syntax-from-char #\{ #\( )
(defun lcurly-brace-reader (stream inchar) ; this was way too easy to do.
(declare (ignore inchar))
(read-delimited-list #\} stream t))
(set-macro-character #\{ #'lcurly-brace-reader)
(set-macro-character #\} (get-macro-character #\) ))
(set-syntax-from-char #\} #\) )
;; un-lisp -- make parens meaningless
(set-syntax-from-char #\) #\] ) ; ( and ) become normal braces
(set-syntax-from-char #\( #\[ )
lcurly-brace-reader
),当读取器看到{时会调用这个函数,并使用set-macro-character
将该函数分配给{。然后你告诉Lisp,(和)就像[和]一样(也就是说,不是有意义的语法)。我并不是Lisp专家,甚至不是Lisp程序员,但在尝试该语言后,我得出结论:使用一段时间后,括号开始变得“无形”,你开始把代码看成自己想要的样子。你开始更加关注通过s-exprs和宏创建的句法结构,而不是列表和圆括号的词汇形式。
如果你利用一个帮助缩进和语法着色的好编辑器(尝试将括号设置为与背景非常相似的颜色),这一点尤其正确。
你可能无法完全替换语言并获得“Ruby”语法,但你并不需要它。由于语言的灵活性,如果你想,你可以最终拥有一种感觉像你正在遵循“Ruby编程风格”的方言,无论你对此的理解是什么。
我知道这只是一种经验观察,但我认为当我意识到这一点时,我曾经有过Lisp启示时刻。
我见过的关于Lisp宏的最好解释在这里,讲者是Peter Seibel,他是《Practical Common Lisp》一书的作者,也是最好的Lisp教材。
通常很难解释使用Lisp宏的动机,因为它们真正发挥作用的情况太复杂,无法在简单的教程中呈现。但是Peter提供了一个很好的例子,你可以完全理解它,并且它充分、适当地使用了Lisp宏。
你问:“你能否将LISP的函数性质转换为更具面向对象的语法和语义。” 答案是肯定的。实际上,Lisp最初根本没有任何面向对象的编程,这并不奇怪,因为Lisp早在面向对象编程之前就已存在!但是,当我们在1978年第一次了解面向对象编程时,我们能够轻松地将其添加到Lisp中,其中包括使用宏等。最终开发出了Common Lisp Object System(CLOS),这是一个非常强大的面向对象编程系统,可以优雅地适应Lisp。整个系统可以作为扩展加载-- 没有任何内置功能!所有功能都是由宏实现的。
Lisp还具有一种完全不同的功能,称为“读取器宏”,可用于扩展语言的表面语法。使用读取器宏,您可以创建具有类似C或Ruby语法的子语言。它们在内部将文本转换为Lisp。大多数真正的Lisp程序员并不广泛使用这些宏,主要是因为很难扩展交互式开发环境以理解新语法。例如,Emacs缩进命令可能会被新语法所困惑。但是,如果你精力充沛,那么Emacs也是可扩展的,你可以教它理解你的新词法语法。
(function toto)
比在:
function(toto);
而且在
(if tata (toto)
(titi)
(tutu))
不超过:
if (tata)
toto();
else
{
titi();
tutu();
}
我看到括号和分号用得少了。
常规宏操作列表对象。通常,这些对象是其他列表(因此形成树形结构)和符号,但它们也可以是其他对象,例如字符串、哈希表、用户定义的对象等。这些结构称为s-exps。
因此,当您加载源文件时,Lisp编译器将解析文本并生成s-exps。宏在这些上操作。这非常有效,并且是一种神奇的方式,在s-exps的精神内扩展语言。
此外,通过“读取器宏”,上述解析过程可以扩展,该宏允许您自定义编译器将文本转换为s-exps的方式。但我建议您接受Lisp的语法,而不是将其弯曲成其他东西。
当您提到Lisp的“功能性质”和Ruby的“面向对象语法”时,您似乎有点困惑。我不确定“面向对象语法”应该是什么,但Lisp是一种多范式语言,极其支持面向对象编程。
顺便说一下,当我说Lisp时,我指的是Common Lisp。
我建议你放下偏见,认真试一试Lisp。
是的,你可以从根本上改变语法,甚至避免“括号地狱”。为此,你需要定义一个新的读取器语法。请查看读取器宏。
然而,我怀疑要达到编写这些宏的Lisp专业水平,你需要深入了解这种语言,以至于你不再认为括号是“地狱”。也就是说,当你知道如何避免它们时,你会开始接受它们作为一件好事。