代码之家  ›  专栏  ›  技术社区  ›  Alexander Engelhardt

什么是“编写其他程序的Lisp程序”?

  •  3
  • Alexander Engelhardt  · 技术社区  · 6 年前

    在通读时 Paul Graham's Essays ,我对Lisp越来越好奇。

    this article ,他提到最强大的功能之一是 编写编写其他程序的程序 .

    我在他的网站或其他地方找不到直观的解释。有没有一个最小的Lisp程序来演示这是如何实现的?或者,你能用语言解释一下这到底意味着什么吗?

    3 回复  |  直到 6 年前
        1
  •  6
  •   Basile Starynkevitch    6 年前

    口齿不清 homoiconic . 下面是一个函数,它构建一个表示和的s表达式。

    (defun makes(x) (list '+ x 2))
    

    所以 (makes 5) 评估为 (+ 5 2) 这是一个有效的s表达式。你可以把它传给 eval

    使用Lisp宏有更复杂的示例。另见 this . 阅读关于 Evaluation and Compilation 共同的Lisp HyperSpec compile , defmacro , eval 表格)。注意 multi-staged programming .

    我强烈推荐阅读 SICP (可以免费下载)然后 Lisp In Small Pieces . 你也可以享受阅读 Gödel, Escher, Bach.... 和J.Pitrat的博客 Bootstrapping Artificial Intelligence .

    顺便说一下,在POSIX上使用C,您还可以编写程序 generating C代码(或使用 GCCJIT LLVM ),将生成的代码编译为插件,然后 dlopen -正在使用它。

        2
  •  2
  •   djeis    6 年前

    虽然同质性是使这一点变得简单的基本属性,但在实践中,这一点的一个很好的例子是许多lisp中存在的宏功能。同象性允许您编写使用lisp源(表示为列表列表)的lisp函数,并对其执行列表操作操作以生成其他lisp源。宏是一个简单的lisp函数,它作为语言语法的扩展安装在lisp的编译器/求值器中。宏像普通函数一样被调用,但是编译器没有等到运行时才将宏参数的原始代码传递给它。然后宏负责返回一些替代代码,以便编译器在其位置进行处理。

    一个简单的例子是内置的 when 宏,使用方式类似于so(假设某个变量 x ):

    (when (evenp x)
      (print "It's even!")
      (* 5 x))
    

    什么时候 类似于更基本的 if ,但在哪里 如果 接受3个子表达式(test,then case,else case) 什么时候 接受测试,然后在“then”情况下运行任意数量的表达式(它返回 nil 在其他情况下)。使用 如果 您需要一个显式块(a progn 口齿不清):

    (if (evenp x)
        (progn
          (print "It's even!")
          (* 5 x))
        nil)
    

    翻译 什么时候 版本 如果 版本是一些非常简单的列表管理:

    (defun when->if (when-expression)
      (list 'if
            (second when-expression)
            (append (list 'progn)
                    (rest (rest when-expression)))))
    

    尽管我可能会使用列表模板语法和一些较短的函数来获得:

    (defun when->if (when-expression)
      `(if ,(second when-expression) (progn ,@(cddr when-expression)) nil))
    

    这被称为: (when->if (list 'when (list 'evenp 'x) ...)) .

    现在我们需要做的就是通知编译器当它看到一个 (when ...) (实际上,我正在为 (my-when ...) 为了避免与内置版本冲突),它应该使用类似于 when->if 把它变成它能理解的代码。实际的宏语法实际上允许您将表达式/列表(“destructure”it)作为宏参数的一部分拆分,因此它最终看起来像这样:

    (defmacro my-when (test &body then-case-expressions)
      `(if ,test (progn ,@then-case-expressions) nil))
    

    sorta看起来像一个常规函数,只是它获取代码并输出其他代码。现在我们可以写了 (my-when (evenp x) ...) 一切正常。

    lisp宏工具构成了lisp表达能力的一个主要组成部分——它们允许您对语言进行建模,以更好地适应您的项目,并抽象出几乎所有的样板。宏可以简单到 什么时候 或者复杂到让第三方OOP库感觉像是语言的一个一流部分(事实上,许多lisp仍然将OOP实现为一个纯粹的lisp库,而不是核心编译器的一个特殊组件,而不是从使用它们可以看出的)。

        3
  •  0
  •   Simeon Ikudabo    5 年前

    一个很好的例子是Lisp宏。它们不求值,而是转换为其中的表达式。这就是为什么他们本质上是编写程序的程序。它们在编译时和运行时之间转换其中的表达式。这意味着您基本上可以创建自己的语法,因为宏实际上没有求值。一个很好的例子是这个无效的公共lisp表单:

    (backwards ("Hello world" nil format))
    

    显然,format函数的语法是向后的。但是。。。我们将它传递给一个未计算的宏,因此不会得到回溯错误,因为宏实际上没有计算。以下是宏的外观:

     (defmacro backwards (expr)
       (reverse expr))
    

    如您所见,我们在宏中反转表达式,这就是为什么它在编译时和运行时之间成为标准的Lisp表单的原因。我们用一个简单的例子从本质上改变了Lisp的语法。对宏的调用不计算,而是转换。一个更复杂的例子是用html创建网页:

    (defmacro standard-page ((&key title href)&body body)
       `(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
           (:html :lang "en"
          (:head
            (:meta :charset "utf-8")
            (:title ,title)
            (:link :rel "stylesheet"
               :type "text/css"
               :href ,href))
         ,@body)))
    

    我们基本上可以创建一个宏,对该宏的调用将不会被计算,但它将扩展为有效的lisp语法,并且将被计算。如果我们看一下宏扩展,我们可以看到扩展是被计算的:

    (pprint (macroexpand-1 '(standard-page (:title "Hello"
                         :href "my-styles.css")
                  (:h1 "Hello world"))))
    

    扩展到:

    (WITH-HTML-OUTPUT-TO-STRING (*STANDARD-OUTPUT* NIL :PROLOGUE T :INDENT T)
      (:HTML :LANG "en"
         (:HEAD (:META :CHARSET "utf-8") (:TITLE "Hello")
         (:LINK :REL "stylesheet" :TYPE "text/css" :HREF "my-styles.css"))
         (:H1 "Hello world")))
    

    这就是为什么Paul Graham提到你可以编写程序,而ViaWeb本质上是一个大宏。像这样的一堆宏正在编写可以编写代码的代码。。。