我听说Lisp的宏系统非常强大。但是,我发现很难找到一些可用于它们的实际例子;没有它们就很难实现的事情。
有人可以提供一些例子吗?
答案 0 :(得分:40)
源代码转换。所有种类。例子:
新控制语句:您需要WHILE语句吗?你的语言没有?为什么等待仁慈的独裁者可能在明年加一个。自己写吧。五分钟后。
更短的代码:您需要二十个几乎看起来相同的类声明 - 只有有限数量的地方不同。编写一个宏差异表格,将差异作为参数,并为您生成源代码。想稍后改变吗?在一个地方更改宏。
源树中的替换:您想要在源树中添加代码吗?变量真的应该是函数调用吗?围绕“遍历”源代码的代码包裹宏,并更改找到变量的位置。
后缀语法:您想以后缀形式编写代码吗?使用将代码重写为普通形式的宏(Lisp中的前缀)。
编译时效果:您需要在编译环境中运行一些代码,以便向开发环境通知定义?宏可以生成在编译时运行的代码。
编译时的代码简化/优化:您希望在编译时简化一些代码吗?使用执行简化的宏 - 这样您就可以根据源表单将工作从运行时转移到编译时。
从描述/配置生成代码:您需要编写复杂的类组合。例如,你的窗口有一个类,子窗格有类,窗格之间有空间限制,你有一个命令循环,一个菜单和一大堆其他东西。编写一个宏来捕获窗口及其组件的描述,并根据描述创建驱动应用程序的类和命令。
语法改进:有些语言语法看起来不太方便吗?编写一个宏,使您,应用程序编写者更方便。
特定于域的语言:您需要一种更接近应用程序域的语言吗?使用一堆宏创建必要的语言表单。
元语言抽象
基本思想:语言层面上的所有内容(新形式,新语法,表单转换,简化,IDE支持......)现在都可以由开发人员逐个编程 - 没有单独的宏处理阶段。
答案 1 :(得分:9)
选择任何“code generation tool”。阅读他们的例子。这就是它能做的。
除非您不需要使用其他编程语言,否则请将宏扩展代码放在使用宏的地方,运行单独的命令来构建,或者在硬盘上放置额外的文本文件,这些文件只是有价值的你的编译器。
例如,我相信阅读Cog示例应该足以让任何Lisp程序员哭泣。
答案 2 :(得分:4)
您通常希望在预处理器中完成的任何事情吗?
我写的一个宏是用于定义用于驱动游戏对象的状态机。读取代码(使用宏)比读取生成的代码更容易:
(def-ai ray-ai
(ground
(let* ((o (object))
(r (range o)))
(loop for p in *players*
if (line-of-sight-p o p r)
do (progn
(setf (target o) p)
(transit seek)))))
(seek
(let* ((o (object))
(target (target o))
(r (range o))
(losp (line-of-sight-p o target r)))
(when losp
(let ((dir (find-direction o target)))
(setf (movement o) (object-speed o dir))))
(unless losp
(transit ground)))))
比阅读:
(progn
(defclass ray-ai (ai) nil (:default-initargs :current 'ground))
(defmethod gen-act ((ai ray-ai) (state (eql 'ground)))
(macrolet ((transit (state)
(list 'setf (list 'current 'ai) (list 'quote state))))
(flet ((object ()
(object ai)))
(let* ((o (object)) (r (range o)))
(loop for p in *players*
if (line-of-sight-p o p r)
do (progn (setf (target o) p) (transit seek)))))))
(defmethod gen-act ((ai ray-ai) (state (eql 'seek)))
(macrolet ((transit (state)
(list 'setf (list 'current 'ai) (list 'quote state))))
(flet ((object ()
(object ai)))
(let* ((o (object))
(target (target o))
(r (range o))
(losp (line-of-sight-p o target r)))
(when losp
(let ((dir (find-direction o target)))
(setf (movement o) (object-speed o dir))))
(unless losp (transit ground)))))))
通过将整个状态机生成封装在宏中,我还可以确保我只引用定义的状态并警告是否不是这种情况。
答案 3 :(得分:3)
使用宏,您可以定义自己的语法,从而扩展Lisp并使其成为 适合您编写的程序。
查看非常好的在线图书Practical Common Lisp,了解实际案例。
7. Macros: Standard Control Constructs
8. Macros: Defining Your Own
答案 4 :(得分:3)
除了扩展语言的语法以使您能够更清楚地表达自己,它还可以让您控制评估。尝试用您选择的语言编写自己的if
,这样您就可以实际编写my_if something my_then print "success" my_else print "failure"
并且不会同时评估两个打印语句。在没有足够强大的宏系统的任何严格语言中,这是不可能的。但是,没有Common Lisp程序员会发现任务太具挑战性。同上for
- 循环,foreach
循环等等。你不能在C中表达这些东西,因为它们需要特殊的评估语义(人们实际上试图将foreach
引入Objective-C,但它不能很好地工作),但由于它的宏,它们在Common Lisp中几乎是微不足道的。
答案 5 :(得分:3)
R具有宏(R manual, chapter 6)。您可以使用它来实现函数lm()
,它根据您指定为代码的模型分析数据。
以下是它的工作原理:lm(Y ~ aX + b, data)
会尝试查找最适合您数据的a
和b
参数。很酷的部分是,你可以用aX + b
的任何线性方程代替它,它仍然可以工作。这是一个非常出色的功能,可以使统计计算更容易,并且它只能如此优雅地工作,因为lm()
可以分析它给出的方程式,这正是Lisp宏所做的。
答案 6 :(得分:2)
只是一个猜测 - 领域特定语言。
答案 7 :(得分:1)
宏对提供语言功能的访问至关重要。例如,在TXR Lisp中,我有一个名为sys:capture-cont
的函数用于捕获分隔的延续。但这本身就很难用。因此,它周围有一些宏,例如suspend
或obtain
and yield
,它们提供了可恢复,暂停执行的替代模型。它们已实施here。
另一个例子是复杂的宏defstruct
,它提供了定义结构类型的语法。它将其参数编译为lambda
- s和传递给函数make-struct-type
的其他材料。如果程序直接使用make-struct-type
来定义OOP结构,那么它们将是丑陋的:
1> (macroexpand '(defstruct foo bar x y (z 9) (:init (self) (setf self.x 42))))
(sys:make-struct-type 'foo 'bar '()
'(x y z) ()
(lambda (#:g0101)
(let ((#:g0102 (struct-type #:g0101)))
(unless (static-slot-p #:g0102 'z)
(slotset #:g0101 'z
9)))
(let ((self #:g0101))
(setf (qref self x)
42)))
())
糟糕!有很多事情要做对。例如,我们不会将9
粘贴到插槽z
中,因为(由于继承)我们实际上可能是派生结构的基础结构,并且在派生结构中z
1}}可以是静态槽(由实例共享)。我们将破坏派生类中z
的值。
在ANSI Common Lisp中,宏的一个很好的例子是loop
,它为并行迭代提供了一个完整的子语言。单个loop
调用可以表达整个复杂的算法。
宏让我们独立思考语言特性中我们想要的语法,以及实现它所需的底层函数或特殊运算符。无论我们在这两者中做出什么选择,宏都会为我们搭建它们。我不必担心make-struct
难以使用,所以我可以专注于技术方面;我知道无论我如何进行各种权衡,宏看起来都是一样的。我做出了设计决定,所有结构初始化将由注册到该类型的一些函数完成。好的,这意味着我的宏必须在插槽定义语法中进行所有初始化,并编译匿名函数,其中插槽初始化由正文中生成的代码完成。
宏是语法位的编译器,函数和特殊运算符是目标语言。
有时人们(通常是非Lisp人)会以这种方式批评宏:宏不会添加任何功能,只会添加语法糖。
首先,语法糖是一种能力。
其次,您还必须从总体黑客角度考虑宏,并将宏与实现级工作相结合。如果我在Lisp方言中添加功能,例如结构或延续,我实际上正在扩展功能。宏在该企业中的参与是必要的。尽管宏并不是新能力的源(它不是从宏本身发出的),但它们有助于驯服和利用它,使其表达。
如果您没有sys:capture-cont
,则无法通过suspend
宏破解其行为。但是如果你没有宏,那么你必须做一些非常不方便的事情来提供访问到一个不是库函数的新功能,即硬编码一些新的短语结构规则到解析器。