一个语言可以有没有括号的Lisp强大的macros?
一个语言可以有没有括号的Lisp强大的macros?
当然,问题是使用起来是否方便。
我们先来看看Lisp是如何略有不同的。
Lisp有一个两阶段的语法。
A)有sexpression式的数据语法
例子:
(mary called tim to tell him the price of the book) (sin ( x ) + cos ( x ))
sexpression式是primefaces列表(列表列表)。
B)那么在sexpression式的顶部有Lisp语言语法。 并不是每个sexpression式都是有效的Lisp程序。
(3 + 4)
不是一个有效的Lisp程序。
(+ 3 4)
是一个有效的Lisp程序。
有趣的部分是现在可以读取sexpression式,然后Lisp使用正常的数据结构来表示它们。
大多数其他编程语言没有内部源的基本表示 – 除了string。
内在的源代码现在可以很容易地计算代码。
我们来看看无效的Lisp代码:
(3 + 4)
该程序
(defun convert (code) (list (second code) (first code) (third code))) (convert '(3 + 4)) -> (+ 3 4)
已将中缀expression式转换为有效的前缀expression式。
(eval (convert '(3 + 4))) -> 7
EVAL评估转换的源代码。
编程语言现在至less有三种select可以进行这样的计算:
-
基于string转换的源代码转换
-
使用类似Lisp的原始数据结构。 这个更复杂的变体是基于XML的语法。 然后可以转换XMLexpression式。 还有其他可能的外部格式与内部数据相结合。
-
使用真正的语法描述格式,并使用表示语法类别的数据结构将源代码内化为语法树。
对于所有这些方法,您将find编程语言。 Lisp或多或less处于第2阵营。其结果是:理论上讲,它不是真正令人满意的,并且使静态分析源代码(如果代码转换基于任意Lisp函数)是不可能的。 几十年来,Lisp社区一直在与之斗争(例如,参见Scheme社区尝试过的无数方法)。 幸运的是,与其他一些替代品相比,它使用起来相对容易,function也相当强大。 变体1是简单的丑陋。 变体3在简单和复杂的转换中导致很多复杂性。
另一个问题是如何转换代码。 一种方法将基于转换规则(如在一些Schememacros变体中)。 另一种方法是一种特殊的转换语言(就像可以进行任意计算的模板语言)。 Lisp方法是使用Lisp本身。 这使得使用完整的Lisp语言编写任意转换成为可能。 在Lisp中没有单独的parsing阶段,但是在任何时候都可以读取,转换和评估expression式。
Lisp对于代码转换来说是一种本地最大的简单性。
另请注意,READ将sexpression式读入内部数据。 在Lisp中,可以为不同的外部语法使用不同的读取器,也可以重新使用Lisp内置读取器,并使用读取macros机制重新编程它。 在Lisp中提供了两种不同的外部语法的例子。
当前的Lisp语法在Lisp程序员中很受欢迎,原因有两个:
1)数据是代码是数据的思想,可以很容易地写出基于内部数据的各种代码转换。 从阅读代码,操作代码到打印代码,还有一个相对直接的方法。
2)文本编辑器可以直接编程来操纵sexpression式。 这使编辑器中的代码和数据转换相对容易。
绝对。 如果你不得不面对一个复杂的语法,这只是一个更复杂的几个数量级。 正如Peter Norvig指出的那样 :
Python可以访问程序的抽象语法树,但是这并不是心存隐晦的。 从好的一面来看,这些模块很容易理解,用五分钟五行的代码就可以得到这个结果:
>>> parse("2 + 2")
['eval_input', ['testlist', ['test', ['and_test', ['not_test', ['comparison',
['expr', ['xor_expr', ['and_expr', ['shift_expr', ['arith_expr', ['term',
['factor', ['power', ['atom', [2, '2']]]]], [14, '+'], ['term', ['factor',
['power', ['atom', [2, '2']]]]]]]]]]]]]]], [4, ''], [0, '']]
这对我来说是一个失望。 等效expression式的Lispparsing是
(+ 2 2)
。 似乎只有一个真正的专家会想操纵Pythonparsing树,而Lispparsing树对任何人来说都很简单。 还有可能通过连接string来创build类似于Python的macros,但是它并没有与其他语言集成,所以在实践中并没有完成。
由于我不是超级天才(或者甚至是Peter Norvig),我会坚持(+ 2 2)
。
下面是Rainer答案的简短版本:
为了拥有lisp风格的macros,你需要一种在数据结构中表示源代码的方法。 在大多数语言中,唯一的“源代码数据结构”是一个string,它没有足够的结构来允许你做真正的macros。 有些语言提供了一个真正的数据结构,但是它太复杂了,就像Python一样,所以编写真正的macros是非常复杂的,并不值得。
Lisp的名单和括号在中间击中了最佳位置。 只要结构足够容易处理,但不要太复杂。 作为一个好处,当你嵌套列表时,你会得到一棵树,恰好是编程语言自然采用的结构(几乎所有的编程语言在被实际解释/编译之前首先被parsing为“抽象语法树”或AST )。
基本上,编程Lisp直接写一个AST,而不是写一些其他语言,然后由计算机变成一个AST。 你可能会放弃parens,但你只需要一些其他的方式来分组成一个列表/树。 这样做可能不会获得太多收益。
括号与macros无关。 这只是Lisp的做事方式。
例如,Prolog有一个非常强大的叫做“term expansion”的macros机制。 基本上,每当Prolog读取一个T项时,如果尝试一个特殊的规则term_expansion(T, R)
。 如果成功,R的内容被解释而不是T.
更不用提Dylan语言了 ,它具有非常强大的句法macros观系统,其特点(除别的之外)是参照透明度,而是中缀(Algol风格)语言。
是。 Lisp中的圆括号以经典的方式用作分组机制。 缩进是expression群体的另一种方式。 例如下面的结构是等价的:
A ((BC) D)
和
A B C D
看看Sweet-expressions 。 Wheeler提出了一个很好的例子,中缀表示法之前没有工作的原因是,典型的表示法也试图增加优先级,这增加了复杂性,这在编写macros时造成困难。
出于这个原因,他提出了像{1 + 2 + 3}和{1 + {2 * 3}}这样的中缀语法(注意符号之间的空格),并将其翻译为(+ 1 2)和(+ 1 3))。 他补充说,如果有人写{1 + 2 * 3},它应该变成(nfx 1 + 2 * 3),如果你真的想提供优先权,那么它应该被捕获,但是作为默认值,会是一个错误。
他还build议,缩进应该是重要的,提出函数可以被称为fn(ABC)以及(ABC),将数据[A]转换成(括号访问数据A),并且整个系统应该是与sexpression式兼容。
总的来说,这是一个有趣的build议,我想广泛试验。 (但是不要告诉comp.lang.lisp中的任何人:他们会为了好奇而烧你的股份:-)。
Erlang的分析转换与Lispmacros相似,尽pipe它们的编写和使用要复杂得多(它们应用于整个源文件,而不是按需调用)。
Lisp本身以Mexpression式的forms对非括号语法进行了简短的调整。 它并没有与社区一起使用,尽pipe这个想法的变种已经进入了现代的Lisp,所以你可以在Lisp中得到没有括号的Lisp强大的macros!
在Tcl中以与Lispmacros类似的方式进行代码重写是一种常用技术。 例如,这是(简单的)代码,可以使编写总是导入一组全局variables的过程更容易:
proc gproc {name arguments body} { set realbody "global foo bar boo;$body" uplevel 1 [list proc $name $arguments $realbody] }
有了这个,用gproc xyz
而不是proc xyz
声明的所有过程都可以访问foo
, bar
和boo
全局variables。 整个关键在于uplevel
在调用者的上下文中接受一个命令并对其进行评估, list
(除其他之外)是替代安全的代码片段的理想构造器。
是的,你可以肯定有没有所有括号的Lispmacros。
看看“甜蜜的表情”,它为传统的sexpression提供了一系列附加的缩写。 它们添加了缩进,中缀的方式,以及像f(x)这样的传统函数调用,但是以一种向后兼容的方式(可以自由地混合格式良好的sexpression式和甜蜜expression式),generics的,同心的。
Sweetexpression式是在http://readable.sourceforge.net上开发的,有一个示例实现。;
对于计划,有一个SRFI甜蜜的expression,SRFI-110: http ://srfi.schemers.org/srfi-110/
不,这是没有必要的。 任何能够让你对分析树进行某种访问的东西都足以让你像在Common Lisp中一样操作macros体。 然而,由于在lisp中对AST的操纵与对列表的操纵是一样的(在lisp家族中容易接近),如果没有“parsing树”和“书写forms”,它可能就不那么自然了一样。
我想这个没有提到。
C ++模板是图灵完成的,并在编译时执行处理。
有一个众所周知的expression式模板机制,允许转换,而不是从任意代码,但至less,从c + +运算符的子集。
所以想象你有3个1000个元素的向量,你必须执行:
(A + B + C)[0]
您可以在expression式模板中捕获此树,并在编译时随意对其进行操作。
有了这个树,在编译时,可以转换expression式。 例如,如果该expression式意味着您的域的A[0] + B[0] + C[0]
,则可以避免正常的c ++处理:
- 添加A和B,添加1000个元素。
- 创build一个临时的结果,并添加1000个元素的C.
- 索引结果以获得第一个元素。
并用另一个转换后的expression式模板树进行replace:
- 捕捉A [0]
- 捕捉B [0]
- 捕捉C [0]
- 将所有3个结果一起添加到结果中,以+ =避免临时对象返回。
我认为这不比lisp好,但它仍然非常强大。
是的,这当然是可能的。 特别是如果它仍然是一个Lisp的帽子下:
嘘有一个很好的“引用”macros语法,使用[| |]作为分隔符,并且具有某些replace,这些replace实际上是由使用$variables的编译器pipe道在语法上validation的。 尽pipe使用起来简单而且相对来说比较轻松,但是在编译器方面实现要比sexpression式复杂得多。 Boo的解决scheme可能有一些限制,并没有影响我自己的代码。 还有一种更像普通的OO代码的替代语法,但是这样的语法就属于“不适合心脏不好”类别,比如处理Ruby或者Pythonparsing树。
Javascript的模板string提供了另一种方法来处理这种事情。 例如,Mark S. Miller的quasiParserGenerator为parsing器实现了语法语法。