什么是"Lisp program that writes other programs"?



在阅读Paul Graham的散文时,我对Lisp越来越好奇。

在这篇文章中,他提到最强大的功能之一是您可以编写编写其他程序的程序

我在他的网站或其他地方找不到直观的解释。有没有一些最小的Lisp程序可以展示如何做到这一点的例子?或者,你能用语言解释一下这到底意味着什么吗?

Lisp是同源的。这里有一个函数,它构建了一个表示和的s-表达式。

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

因此CCD_ 1评估为CCD_。你可以把它传给eval

Lisp宏还有更复杂的例子。另请参见此。阅读Common Lisp HyperSpec的评估和编译部分(还请注意其compiledefmacroeval形式)。注意多阶段编程。

我强烈建议阅读SICP(它是免费下载的),然后读《小片段的Lisp》。你也可以喜欢阅读哥德尔、埃舍尔、巴赫。。。。以及J.Pitrat关于Bootstrapping人工智能的博客。

顺便说一句,在POSIX上使用C,您也可以编写生成C代码的程序(或使用GCCJIT或LLVM),将生成的代码编译为插件,并对其进行dlopen。

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

一个简单的例子是内置的when宏,这样使用(假设某个变量x):

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

when类似于更基本的(makes 5)0,但其中if采用3个子表达式(test,then case,else case)when进行测试,然后在"then"情况下运行任意数量的表达式(在else情况下返回nil)。要使用if写这篇文章,您需要一个显式块(Common Lisp中的progn):

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

when版本转换为if版本是一些非常简单的列表操作:

(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 ...)这样的表达式时(实际上,我正在为(+ 5 2)0编写一个表达式,以避免与内置版本发生冲突),它应该使用像我们的when->if这样的东西将其转换为它能理解的代码。实际的宏语法实际上可以让你把表达式/列表("destructure"it)作为宏参数的一部分,所以它最终看起来像这样:

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

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

lisp宏工具构成了lisp表达能力的主要组成部分——它们允许你塑造语言,以更好地适应你的项目,并抽象掉几乎所有的样板。宏可以像when一样简单,也可以足够复杂,让第三方OOP库感觉像是语言的一流部分(事实上,许多lisp仍然将OOP实现为一个纯lisp库,而不是核心编译器的一个特殊组件,你无法从使用它们中分辨出来)。

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本质上是一个大宏。一堆像这样的宏正在编写可以编写代码的代码。。。

最新更新