代码之家  ›  专栏  ›  技术社区  ›  yazz.com

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

  •  60
  • yazz.com  · 技术社区  · 14 年前

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

    7 回复  |  直到 10 年前
        1
  •  86
  •   Rainer Joswig Michael Fox    14 年前

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

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

    这其中有一些方面:如果您询问有关“重新定义”语言的问题,那么严格地重新定义意味着重新定义一些现有的语言机制(语法、语义、语用学)。但也有语言功能的扩展、嵌入和删除。

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

    一些方法可以重新定义/更改/扩展主要常见的Lisp实现提供的功能:

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

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

    • 通知功能 . 通常,人们希望向函数添加一些行为。这在Lisp世界被称为“建议”。许多常见的Lisp实现将提供这种功能。

    • 定制包 . 包将符号分组在名称空间中。common-lisp包是作为ANSI common lisp标准一部分的所有符号的所在地。程序员可以创建新的包并导入现有的符号。因此,您可以在程序中使用一个扩展的公共lisp包,它提供更多或不同的工具。只需添加(包中的“extended-common-lisp”),您就可以开始使用自己的common lisp扩展版本进行开发。根据使用的名称空间,您使用的Lisp方言可能看起来很细微,甚至完全不同。在Lisp机器上的一般情况下,有几个Lisp方言以这种方式并排出现:zetalsp、cltl1、ansi common lisp和symbolics common lisp。

    • 克洛斯 和动态对象。通用的lisp对象系统内置了变更。元对象协议扩展了这些功能。CLO本身可以在CLO中扩展/重新定义。你想要不同的继承权。写一个方法。您需要不同的方法来存储实例。写一个方法。插槽应该有更多的信息。为此提供一个类。CLOS本身的设计使得它能够实现不同面向对象编程语言的整个“区域”。典型的例子是添加原型、与外部对象系统的集成(比如目标C)、添加持久性……

    • LISP表格 .Lisp表单的解释可以用宏重新定义。宏可以解析它所包含的源代码并对其进行更改。有多种方法可以控制转换过程。复杂宏使用代码查询器,它可以理解Lisp表单的语法并可以应用转换。宏可以是琐碎的,但也可以像循环或迭代宏那样变得非常复杂。其他典型的例子是用于嵌入式SQL和嵌入式HTML生成的宏。宏还可以用来移动计算以编译时间。由于编译器本身就是一个lisp程序,所以可以在编译期间进行任意计算。例如,如果在编译期间已知某些参数,Lisp宏可以计算公式的优化版本。

    • 符号 . 公共lisp提供符号宏。符号宏允许更改源代码中符号的含义。一个典型的例子是:(使用插槽(foo)条(+foo 17)),这里用插槽包围的源中的符号foo将替换为调用(插槽值条foo)。

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

    • 条件处理 -处理以某种方式使用编程语言所产生的条件。公共Lisp提供了一种高级的错误处理方法。条件系统也可以用来重新定义语言特性。例如,可以使用自编的自动加载机制来处理未定义的函数错误。当lisp看到一个未定义的函数时,错误处理程序可以尝试自动加载该函数,并在加载必要的代码后重试该操作,而不是在调试器中登录。

    • 特殊变量 -将变量绑定插入现有代码。许多Lisp方言与普通Lisp一样,提供特殊/动态变量。它们的值在运行时堆栈上查找。这允许封闭代码添加影响现有代码的变量绑定,而不更改它。一个典型的例子是像*标准输出*这样的变量。可以重新绑定变量,并且在新绑定的动态范围内使用该变量的所有输出都将转到新的方向。RichardStallman认为这对他来说非常重要,因为它在EmacsLisp中是默认的(尽管Stallman知道Scheme和CommonLisp中的词汇绑定)。

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

        2
  •  14
  •   Svante    14 年前

    在定义新的语法时,我将介绍该方案不同于普通的Lisp。它允许您使用 define-syntax 无论源代码在何处使用,都会应用到它们。它们看起来就像函数,只是在编译时运行并转换AST。

    下面是一个如何 let 可以定义为 lambda . 线与 要匹配的图案和线条 兰姆达 是生成的代码模板。

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

    请注意,这和文本替换完全不同。你可以重新定义 兰姆达 以及上述定义 仍然有效,因为它使用的是 兰姆达 在环境中 定义。基本上,它像宏一样强大,但像函数一样干净。

        3
  •  4
  •   Noah Lavine    14 年前

    宏通常是这样说的原因。其思想是,因为代码只是一个数据结构(树,或多或少),所以您可以编写程序来生成这个数据结构。因此,关于编写生成和操作数据结构的程序,您所知道的一切都增加了您表达代码的能力。

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

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

        4
  •  3
  •   Steffe    10 年前

    我在答案中遗漏了“计算机程序的结构和解释”第4-5章。( link )

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

        5
  •  2
  •   Vatine    14 年前

    尽管部分答案可能适用于Lisp系列中的其他语言,但此答案专门针对普通Lisp(以下简称cl)。

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

    稍微小心一点,编写替换代码并使用它们并不难。

    现在,“普通”阅读器(读取源代码并将其转换为内部符号的部分)希望源代码是一种相当特定的格式(带括号的S表达式),但由于阅读器是由一种称为“读取表”的东西驱动的,开发人员可以创建和修改这些表,因此也可以更改源代码E应该看看。

    这两件事至少应该提供一些理由,解释为什么公共lisp可以被认为是一种可重新编程的编程语言。我手头没有一个简单的例子,但我确实有一个通用lisp到瑞典语的部分实现(创建于4月1日,几年前)。

        6
  •  2
  •   artied    13 年前

    从外面往里看…

    我一直认为这是因为lisp在其核心提供了这样的基本原子逻辑操作符,任何逻辑过程都可以从基本组件中构建(并且已经构建并作为工具集和插件提供)。

    与其说它能够重新定义自己,不如说它的基本定义是如此具有延展性,以至于它可以采用任何形式,而且结构中没有假定或假定任何形式。

    作为一个比喻,如果你只有有机化合物,你就做有机化学;如果你只有金属氧化物,你就做冶金;如果你只有元素,你就可以做所有的事情,但是你有额外的初始步骤要完成……其他人已经为你做了很多……

    我想。。。。。

        7
  •  2
  •   Devon    11 年前

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

    读卡器宏定义X表达式与S表达式共存,例如,

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

    普通香草口齿不清 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 #\<))))
    

    …当然,XML解析器并不是那么简单,但是将它连接到Lisp阅读器中是很简单的。