阅读Paul Graham关于编程语言的文章,你可能会认为Lisp宏是唯一的选择。作为一个忙碌的开发人员,在其他平台上工作,我还没有使用Lisp宏的特权。作为一个想要了解热门话题的人,请解释一下是什么让这个功能如此强大。
请将这一点与我从Python、Java、c#或C开发世界中理解的东西联系起来。
阅读Paul Graham关于编程语言的文章,你可能会认为Lisp宏是唯一的选择。作为一个忙碌的开发人员,在其他平台上工作,我还没有使用Lisp宏的特权。作为一个想要了解热门话题的人,请解释一下是什么让这个功能如此强大。
请将这一点与我从Python、Java、c#或C开发世界中理解的东西联系起来。
当前回答
Lisp宏允许您决定何时(如果有的话)对任何部分或表达式求值。举个简单的例子,想想C语言:
expr1 && expr2 && expr3 ...
它说的是:计算expr1,并且,如果它是正确的,计算expr2,等等。
现在试着把这个&&变成一个函数…没错,你不能。像这样调用:
and(expr1, expr2, expr3)
将在产生答案之前评估所有三个表达式,而不管expr1是否为假!
使用lisp宏,你可以编写如下代码:
(defmacro && (expr1 &rest exprs)
`(if ,expr1 ;` Warning: I have not tested
(&& ,@exprs) ; this and might be wrong!
nil))
现在你有一个&&,你可以像函数一样调用它,它不会计算你传递给它的任何表单,除非它们都为真。
要了解这是如何有用的,请进行对比:
(&& (very-cheap-operation)
(very-expensive-operation)
(operation-with-serious-side-effects))
and:
and(very_cheap_operation(),
very_expensive_operation(),
operation_with_serious_side_effects());
你可以用宏做的其他事情是创建新的关键字和/或迷你语言(例如,查看(loop…)宏),将其他语言集成到lisp中,例如,你可以编写一个宏,让你这样说:
(setvar *rows* (sql select count(*)
from some-table
where column1 = "Yes"
and column2 like "some%string%")
这还没有涉及到Reader宏。
希望这能有所帮助。
其他回答
Lisp宏允许您决定何时(如果有的话)对任何部分或表达式求值。举个简单的例子,想想C语言:
expr1 && expr2 && expr3 ...
它说的是:计算expr1,并且,如果它是正确的,计算expr2,等等。
现在试着把这个&&变成一个函数…没错,你不能。像这样调用:
and(expr1, expr2, expr3)
将在产生答案之前评估所有三个表达式,而不管expr1是否为假!
使用lisp宏,你可以编写如下代码:
(defmacro && (expr1 &rest exprs)
`(if ,expr1 ;` Warning: I have not tested
(&& ,@exprs) ; this and might be wrong!
nil))
现在你有一个&&,你可以像函数一样调用它,它不会计算你传递给它的任何表单,除非它们都为真。
要了解这是如何有用的,请进行对比:
(&& (very-cheap-operation)
(very-expensive-operation)
(operation-with-serious-side-effects))
and:
and(very_cheap_operation(),
very_expensive_operation(),
operation_with_serious_side_effects());
你可以用宏做的其他事情是创建新的关键字和/或迷你语言(例如,查看(loop…)宏),将其他语言集成到lisp中,例如,你可以编写一个宏,让你这样说:
(setvar *rows* (sql select count(*)
from some-table
where column1 = "Yes"
and column2 like "some%string%")
这还没有涉及到Reader宏。
希望这能有所帮助。
lisp宏以程序片段作为输入。这个程序片段被表示为一个数据结构,可以按照您喜欢的任何方式进行操作和转换。最后,宏输出另一个程序片段,这个片段是在运行时执行的。
c#没有宏功能,但是如果编译器将代码解析为CodeDOM树,并将其传递给一个方法,该方法将其转换为另一个CodeDOM,然后将其编译为IL,则会有等效的宏功能。
这可以用来实现“糖”语法,如for each-statement using-clause, linq select-expressions等等,作为转换为底层代码的宏。
如果Java有宏,您就可以在Java中实现Linq语法,而不需要Sun更改基本语言。
下面是在c#中实现lisp风格的宏的伪代码:
define macro "using":
using ($type $varname = $expression) $block
into:
$type $varname;
try {
$varname = $expression;
$block;
} finally {
$varname.Dispose();
}
我不确定我能给每个人的(优秀的)帖子添加一些见解,但是……
Lisp宏工作得很好,因为Lisp语法的本质。
Lisp是一种非常规则的语言(把所有东西都想象成一个列表);宏使您能够将数据和代码视为相同的(不需要字符串解析或其他技巧来修改lisp表达式)。将这两个特性结合起来,就有了一种非常简洁的方式来修改代码。
编辑:我想说的是Lisp是同构的,这意味着Lisp程序的数据结构是用Lisp本身编写的。
因此,您最终可以在语言之上创建自己的代码生成器,使用语言本身的所有功能(例如。在Java中,你必须破解字节码编织的方法,尽管一些框架(如AspectJ)允许你使用不同的方法来做到这一点,但它基本上是一种破解)。
在实践中,使用宏可以在lisp的基础上构建自己的迷你语言,而不需要学习其他语言或工具,并使用语言本身的全部功能。
我认为我从来没有见过比这个家伙解释得更好的Lisp宏:http://www.defmacro.org/ramblings/lisp.html
简而言之,宏是代码的转换。它们允许引入许多新的语法结构。例如,考虑c#中的LINQ。在lisp中,有类似的由宏实现的语言扩展(例如,内置循环构造,迭代)。宏显著地减少了代码重复。宏允许嵌入«小语言»(例如,在c#/java中可以使用xml进行配置,在lisp中可以使用宏实现同样的事情)。宏可能隐藏使用库的困难。
例如,在lisp中你可以写
(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
(format t "User with ID of ~A has name ~A.~%" id name))
这隐藏了所有数据库的东西(事务,正确的连接关闭,获取数据等),而在c#中,这需要创建SqlConnections, SqlCommands,将SqlParameters添加到SqlCommands,在SqlDataReaders上循环,正确地关闭它们。