在阅读Paul Graham's Essays的同时,我对Lisp越来越好奇。
在this article中,他提到了最强大的功能之一就是您可以编写可以编写其他程序的程序。
我在他的网站或其他地方找不到直观的解释。是否有一些最小的Lisp程序显示了如何完成此操作的示例?或者,您可以用语言解释这到底意味着什么吗?
答案 0 :(得分:6)
Lisp是homoiconic。这是一个构建表示和的s表达式的函数。
(defun makes(x) (list '+ x 2))
因此(makes 5)
的计算结果为(+ 5 2)
,它是有效的s表达式。您可以将其传递给eval
Lisp宏有更复杂的示例。另请参见this。阅读Common Lisp HyperSpec的Evaluation and Compilation部分(还请注意其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),将生成的代码编译为插件,然后{{3 }}-
答案 1 :(得分:2)
虽然谐音是使这一过程变得容易的基本特性,但实际上,许多示例中都存在宏设施,这是一个很好的例子。同质性允许您编写使用lisp源(表示为列表列表)的lisp函数,并对其进行列表处理操作以生成其他lisp源。宏是用于执行此操作的普通lisp函数,该函数作为语言语法的扩展安装在lisp的编译器/评估器中。宏的调用方式与普通函数类似,但是无需等待运行时,编译器即可将宏参数的原始代码传递给它。然后,该宏负责返回一些替代代码,供编译器代替它处理。
一个简单的示例是内置的when
宏,其用法如下(假设某个变量x
):
(when (evenp x)
(print "It's even!")
(* 5 x))
when
与更基本的if
相似,但其中if
包含3个子表达式(测试,随后情况,否则情况)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 ...)
的表达式时(实际上我正在为(my-when ...)
写一个表达式,以避免与内置版本冲突),应该使用类似我们的when->if
之类的代码将其转化为它可以理解的代码。实际的宏语法实际上使您可以分解表达式/列表(“分解”它)作为宏参数的一部分,因此最终看起来像这样:
(defmacro my-when (test &body then-case-expressions)
`(if ,test (progn ,@then-case-expressions) nil))
看起来像常规函数一样,只是它要接受代码并输出其他代码。现在我们可以编写(my-when (evenp x) ...)
,一切正常。
lisp宏功能是lisps表达能力的主要组成部分-它们使您可以塑造语言以更好地适合您的项目,并提取几乎所有样板。宏可以像when
一样简单,也可以足够复杂,以使第三方OOP库看起来像是该语言的一流组成部分(实际上,许多lisps仍将OOP实现为纯Lisp库,而不是特殊的核心编译器的组件,而不是使用它们就能分辨出来的。)
答案 2 :(得分:0)
一个很好的例子是Lisp宏。它们不会被求值,而是会转换为其中的表达式。这就是使它们本质上是编写程序的程序的原因。它们在编译时和运行时之间转换其中的表达式。这意味着您实际上可以创建自己的语法,因为实际上并未评估宏。一个很好的例子就是这种无效的普通lisp形式:
(backwards ("Hello world" nil 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本质上是一个大宏。一堆这样的宏可以编写可以编写代码的代码...