Lisp如何让你重新定义语言本身?

Zub*_*air 60 lisp

我听说Lisp允许你重新定义语言本身,我试图研究它,但是在任何地方都没有明确的解释.有没有人有一个简单的例子?

Rai*_*wig 86

Lisp用户将Lisp称为可编程编程语言.它用于符号计算 - 用符号计算.

宏只是利用符号计算范例的一种方式.更广泛的愿景是Lisp提供了简单的方法来描述符号表达式:数学术语,逻辑表达式,迭代语句,规则,约束描述等.宏(Lisp源代码的转换)只是符号计算的一个应用.

有一些方面:如果你问'重新定义'语言,那么严格重新定义将意味着重新定义一些现有的语言机制(语法,语义,语用).但也有扩展,嵌入,删除语言功能.

在Lisp传统中,已经有许多尝试来提供这些功能.Lisp方言和某种实现可能只提供它们的一部分.

主要Common Lisp实现提供的几种重新定义/更改/扩展功能的方法:

  • s表达式语法.s表达式的语法不固定.阅读器(函数READ)使用所谓的读表来指定在读取字符时将执行的函数.可以修改和创建读表.这允许您例如更改列表,符号或其他数据对象的语法.还可以为新的或现有的数据类型(如散列表)引入新语法.也可以完全替换s表达式语法并使用不同的解析机制.如果新解析器返回Lisp表单,则解释器或编译器不需要进行任何更改.一个典型的例子是可以读取中缀表达式的读取宏.在这样的读取宏中,正在使用运算符的中缀表达式和优先级规则.读取宏与普通宏不同:读取宏工作在Lisp数据语法的字符级别上.

  • 替换功能.顶级函数绑定到符号.用户可以更改此绑定.大多数实现都有一种机制,即使对于许多内置函数也允许这样做.如果要提供内置函数ROOM的替代方法,可以替换其定义.某些实现会引发错误,然后提供继续更改的选项.有时需要解锁包裹.这意味着通常可以用新定义替换函数.这方面有局限性.一个是编译器可以在代码中内联函数.要查看效果,需要重新编译使用更改的代码的代码.

  • 建议职能.通常人们想要为函数添加一些行为.这在Lisp世界中被称为"建议".许多Common Lisp实现将提供这样的工具.

  • 自定义包.包将名称空间中的符号分组.COMMON-LISP包是ANSI Common Lisp标准中所有符号的主页.程序员可以创建新包并导入现有符号.因此,您可以在您的程序中使用EXTENDED-COMMON-LISP包,它提供更多或不同的设施.只需添加(IN-PACKAGE"EXTENDED-COMMON-LISP"),您就可以开始使用自己的Common Lisp扩展版本进行开发.根据使用的命名空间,您使用的Lisp方言可能看起来略微不同甚至根本不同.在Lisp机器上的Genera中,有几种Lisp方言并排:ZetaLisp,CLtL1,ANSI Common Lisp和Symbolics Common Lisp.

  • CLOS和动态对象.Common Lisp对象系统内置了更改.元对象协议扩展了这些功能.CLOS本身可以在CLOS中扩展/重新定义.你想要不同的继承.写一个方法.您需要不同的方式来存储实例.写一个方法.插槽应该有更多信息.提供一个类.CLOS本身的设计使其能够实现不同面向对象编程语言的整个"区域".典型的例子是添加原型,与外部对象系统集成(如Objective C),添加持久性,...

  • Lisp形式.可以使用宏重新定义Lisp表单的解释.宏可以解析它包含的源代码并进行更改.有多种方法可以控制转换过程.复杂的宏使用代码walker,它可以理解Lisp表单的语法并可以应用转换.宏可以是微不足道的,但也可以像LOOP或ITERATE宏一样变得非常复杂.其他典型示例是用于嵌入式SQL和嵌入式HTML生成的宏.宏也可用于将计算移动到编译时.由于编译器本身就是一个Lisp程序,因此可以在编译期间进行任意计算.例如,如果在编译期间已知某些参数,则Lisp宏可以计算公式的优化版本.

  • 符号.Common Lisp提供符号宏.符号宏允许更改源代码中符号的含义.一个典型的例子是这样的:(with-slots(foo)bar(+ foo 17))这里用WITH-SLOTS包围的源中的符号FOO将被替换为一个调用(slot-value bar'foo).

  • 优化,使用所谓的编译器宏,可以提供某些功能的更高效版本.编译器将使用这些编译器宏.这是用户编程优化的有效方法.

  • 条件处理 - 处理以某种方式使用编程语言而产生的条件.Common Lisp提供了一种处理错误的高级方法.条件系统还可用于重新定义语言功能.例如,可以使用自编写的自动加载机制处理未定义的函数错误.当Lisp看到未定义的函数时,错误处理程序可能会尝试自动加载该函数并在加载必要的代码后重试该操作,而不是在调试器中登陆.

  • 特殊变量 - 将变量绑定注入现有代码.许多Lisp方言,如Common Lisp,提供特殊/动态变量.它们的值在堆栈的运行时查找.这允许封闭代码以添加影响现有代码的变量绑定,而无需更改它.典型的例子是像*standard-output*这样的变量.可以在新绑定的动态范围内使用此变量重新绑定变量和所有输出将转到新的方向.Richard Stallman认为这对他来说非常重要,因为它在Emacs Lisp中被默认(尽管Stallman知道Scheme和Common Lisp中的词法绑定).

Lisp拥有这些和更多的功能,因为它已被用于实现许多不同的语言和编程范例.一个典型的例子是逻辑语言的嵌入式实现,比如Prolog.Lisp允许用s表达式描述Prolog术语,并且使用特殊的编译器,Prolog术语可以编译为Lisp代码.有时需要通常的Prolog语法,然后解析器会将典型的Prolog术语解析为Lisp表单,然后编译.嵌入式语言的其他示例是基于规则的语言,数学表达式,SQL术语,内联Lisp汇编程序,HTML,XML等等.


Die*_*Epp 15

我要说的是,在定义新语法时,Scheme与Common Lisp不同.它允许您定义模板,使用define-syntax它们可以在任何地方使用源代码.它们看起来就像函数一样,只有它们在编译时运行并转换AST.

这是一个如何let定义的例子lambda.line with let是要匹配的模式,line with lambda是生成的代码模板.

(define-syntax let
  (syntax-rules ()
    [(let ([var expr] ...) body1 body2 ...)
     ((lambda (var ...) body1 body2 ...) expr ...)]))
Run Code Online (Sandbox Code Playgroud)

请注意,这与文本替换不同.您实际上可以重新定义lambda,上面的定义let仍然有效,因为它使用了定义lambda的环境中的let定义.基本上,它像宏一样强大,但功能干净.


Noa*_*ine 5

宏是这么说的常见原因。这个想法是因为代码只是一个数据结构(或多或少是一棵树),你可以编写程序来生成这个数据结构。因此,您所了解的有关编写生成和操作数据结构的程序的所有知识都可以增强您的表达能力。

宏并不是对语言的完全重新定义,至少据我所知(我实际上是一名 Schemer;我可能是错的),因为有一个限制。宏只能获取代码的单个子树,并生成单个子树来替换它。因此,您不能编写整个程序转换的宏,就像那样酷。

然而,宏仍然可以做很多事情——绝对比任何其他语言都能让你做的更多。而且,如果您使用静态编译,则进行整个程序的转换根本不难,因此限制就不那么重要了。