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

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

Lisp用户将Lisp作为可编程编程语言 。 它用于符号计算 – 用符号计算。

macros只是利用符号计算范式的一种方式。 更广阔的视野是,Lisp提供了简单的方法来描述符号expression式:math术语,逻辑expression式,迭代语句,规则,约束描述等等。 macros(Lisp源代码的转换)只是符号计算的一个应用。

有一些方面:如果你询问“重新定义”这个语言,那么重新定义就意味着重新定义一些现有的语言机制(语法,语义,语用学)。 但也有扩展,embedded,删除的语言function。

在Lisp的传统中,有很多尝试提供这些function。 Lisp方言和某种实现可能只提供它们的一个子集。

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

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

  • replacefunction 。 顶级函数绑定到符号。 用户可以改变这个绑定。 大多数实现都有一个机制来允许这个function,甚至许多内置函数。 如果您想提供内置函数ROOM的替代方法,则可以replace其定义。 某些实现会引发错误,然后提供继续进行更改的选项。 有时需要解锁一个包。 这意味着一般的function可以用新的定义来替代。 这是有限制的。 一个是编译器可以在代码中内联函数。 要查看效果,需要重新编译使用已更改代码的代码。

  • build议function 。 通常人们想要为function添加一些行为。 这在Lisp世界中被称为“build议”。 许多Common Lisp实现将提供这样的设施。

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

  • CLOS和dynamic对象。 Common Lisp对象系统随内置变化而来。 元对象协议扩展了这些function。 CLOS本身可以在CLOS中扩展/重新定义。 你想要不同的inheritance。 写一个方法。 你需要不同的方法来存储实例。 写一个方法。 老虎机应该有更多的信息。 为此提供一个类。 CLOS本身被devise成能够实现不同面向对象编程语言的整个“区域”。 典型的例子是添加像原型,与外部对象系统集成(如目标C),添加持久性,…

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

  • 符号 。 Common Lisp提供了符号macros。 符号macros允许更改源代码中符号的含义。 一个典型的例子是这样的:(with-slots(foo)bar(+ foo 17))在这里,带有WITH-SLOTS的源代码中的符号FOO将被一个调用(slot-value bar'foo)replace。

  • 通过所谓的编译器macros,可以提供更高效的某些function版本。 编译器将使用这些编译器macros。 这是用户编程优化的有效方法。

  • 条件处理 – 处理以某种方式使用编程语言所产生的条件。 Common Lisp提供了一种处理错误的高级方法。 条件系统也可以用来重新定义语言function。 例如,可以使用自写的自动载入机制处理未定义的函数错误。 当Lisp看到未定义的函数时,error handling程序可以尝试自动载入函数并在加载必要的代码后重试操作,而不是在debugging器中着陆。

  • 特殊variables – 将variables绑定注入到现有的代码中。 许多Lisp方言,如Common Lisp,提供特殊/dynamicvariables。 它们的值是在堆栈上的运行时查找的。 这允许封装代码添加影响现有代码的可变绑定而不改变它。 一个典型的例子是像* standard-output *这样的variables。 可以重新绑定variables,并且在新绑定的dynamic范围内使用此variables的所有输出将进入新的方向。 Richard Stallman认为这对他来说非常重要,因为它在Emacs Lisp中是默认的(即使Stallman知道Scheme和Common Lisp中的词汇绑定)。

Lisp拥有这些和更多的function,因为它已经被用来实现许多不同的语言和编程范例。 一个典型的例子就是一个逻辑语言的embedded式实现,比如Prolog。 Lisp允许用sexpression式和一个特殊的编译器来描述Prolog术语,Prolog术语可以被编译成Lisp代码。 有时需要使用通常的Prolog语法,然后parsing器会将典型的Prolog术语parsing为Lisp格式,然后进行编译。 embedded式语言的其他例子是基于规则的语言,mathexpression式,SQL术语,内联Lisp汇编程序,HTML,XML等等。

我将在这个scheme中讨论与定义新语法时不同于Common Lisp的方法。 它允许您使用define-syntax来定义模板,无论使用哪个模板,都可以define-syntax其应用到源代码中。 它们看起来就像函数一样,只是在编译时运行并转换AST。

这里是一个如何用lambda来定义let的例子。 带let的行是要匹配的模式,带有lambda的行是生成的代码模板。

 (define-syntax let (syntax-rules () [(let ([var expr] ...) body1 body2 ...) ((lambda (var ...) body1 body2 ...) expr ...)])) 

请注意,这不像文本replace。 你可以重新定义lambdalet的上面定义仍然可以工作,因为它是在定义了let的环境中使用lambda的定义。 基本上,它像macros一样强大,但像函数一样干净。

macros是说这个的通常的原因。 这个想法是因为代码只是一个数据结构(或多或less),你可以编写程序来生成这个数据结构。 所有关于编写生成和操作数据结构的程序的知识都会增加您的编程能力。

macros是不完全的重新定义的语言,至less据我所知(我实际上是一个Schemer,我可能是错的),因为有一个限制。 一个macros只能使用你的代码的一个子树,并生成一个单一的子树来代替它。 所以你不能编写完整的程序转换macros,就像那样酷。

不过,macros依然可以做很多东西 – 绝对比任何其他语言都能让你做到。 而如果你使用的是静态编译,那么做一个全程序转换并不难,所以限制就不那么重要了。

答案中提到“计算机程序的结构和解释”第4-5章是我从答案中遗漏的( 链接 )。

这些章节指导您在Lisp中构build一个Lisp评估器。 我喜欢阅读,因为它不仅展示了如何在新的评估器中重新定义Lisp,还让您了解了Lisp编程语言的规范。

这个答案是关于Common Lisp(CL belowafter)的,虽然答案的一部分可能适用于lisp系列的其他语言。

由于CL使用Sexpression式,并且(主要)看起来像一系列函数应用程序,所以内置函数和用户代码之间没有明显的区别。 主要区别在于“语言提供的东西”在编码环境中的特定包中是可用的。

仔细一点,代码replace并不难,而代之以。

现在,“正常”阅读器(读取源代码并将其转换为内部符号的部分)期望源代码具有相当特定的格式(括号Sexpression式),但是由于阅读器由“表“,这些可以由开发者创build和修改,也可以改变源代码的外观。

这两件事至less应该说明为什么Common Lisp可以被认为是可重编程的编程语言。 我手边没有一个简单的例子,但是我确实已经部分实现了将Common Lisp翻译成瑞典语(几年前创build于4月1日)。

从外面看…

我一直认为这是因为Lisp提供了这样一些基本的primefaces逻辑运算符,它的核心是任何逻辑进程都可以从基本组件构build(并且已经被构build和提供为工具集和加载项)。

它的意义不在于它能够重新定义,因为它的基本定义是如此可塑的,以至于它可以采取任何forms,而且没有任何forms被假定/推定到结构中。

作为一个比喻,如果你只有有机化合物,你做有机化学,如果你只有金属氧化物,你做冶金,但如果你只有元素,你可以做的一切,但你有额外的初步步骤来完成….其他大多数已经为你做了….

我认为…..

很酷的例子在http://www.cs.colorado.edu/~ralex/papers/PDF/X-expressions.pdf

读者macros定义Xexpression式与Sexpression式共存,例如,

 ? (cx <circle cx="62" cy="135" r="20"/>) 62 

简单的香草Common Lisp在http://www.AgentSheets.com/lisp/XMLisp/XMLisp.lisp

 (eval-when (:compile-toplevel :load-toplevel :execute) (when (and (not (boundp '*Non-XMLISP-Readtable*)) (get-macro-character #\<)) (warn "~%XMLisp: The current *readtable* already contains a #/< reader function: ~A" (get-macro-character #\<)))) 

…当然,XMLparsing器不是那么简单,而是把它挂在lisp阅读器上。