Common Lisp允许您编写执行所需源转换的宏。
Scheme为您提供了一个卫生的模式匹配系统,您还可以执行转换。宏在实践中有多有用?保罗·格雷厄姆(Paul Graham)在击败平均水平中说:
Viaweb编辑器的源代码大概是20-25%的宏。
人们实际上最终会用宏做什么事情?
Common Lisp允许您编写执行所需源转换的宏。
Scheme为您提供了一个卫生的模式匹配系统,您还可以执行转换。宏在实践中有多有用?保罗·格雷厄姆(Paul Graham)在击败平均水平中说:
Viaweb编辑器的源代码大概是20-25%的宏。
人们实际上最终会用宏做什么事情?
Answers:
我主要使用宏来添加节省时间的新语言结构,否则将需要大量样板代码。
例如,我最近发现自己想要for-loop
与C ++ / Java类似的命令。但是,作为一种功能性语言,Clojure并没有开箱即用。所以我只是将其实现为一个宏:
(defmacro for-loop [[sym init check change :as params] & steps]
`(loop [~sym ~init value# nil]
(if ~check
(let [new-value# (do ~@steps)]
(recur ~change new-value#))
value#)))
现在我可以做:
(for-loop [i 0 , (< i 10) , (inc i)]
(println i))
在那里,您可以用六行代码构建一个新的通用编译时语言。
这里有些例子:
方案:
define
用于功能定义。基本上,它是定义函数的较短方法。let
用于创建词法范围变量。 Clojure:
defn
,根据其文档:
与(def name(fn [params *] exprs *))或(def name(fn([params *] exprs *)+))相同,其中任何文档字符串或attrs添加到var元数据
for
:列表理解defmacro
:具有讽刺意味?defmethod
,defmulti
:使用多种方法ns
这些宏很多使在抽象级别上编写代码变得容易得多。我认为宏在很多方面都类似于非Lisps中的语法。
绘图库Incanter提供了一些复杂数据操作的宏。
宏对于嵌入某些模式很有用。
例如,Common Lisp没有定义while
循环,但是有do
一个可用于定义循环。
这是On Lisp的示例 。
(defmacro while (test &body body)
`(do ()
((not ,test))
,@body))
(let ((a 0))
(while (< a 10)
(princ (incf a))))
这将打印“ 12345678910”,并且如果您尝试查看会发生什么
macroexpand-1
:
(macroexpand-1 '(while (< a 10) (princ (incf a))))
这将返回:
(DO () ((NOT (< A 10))) (PRINC (INCF A)))
这是一个简单的宏,但是如前所述,它们通常用于定义新的语言或DSL,但是从这个简单的示例中,您已经可以尝试想象如何使用它们。
该loop
宏是一个什么样的宏可以做一个很好的例子。
(loop for i from 0 to 10
if (and (= (mod i 2) 0) i)
collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
with a = 2
collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)
Common Lisp还有另一种称为读取器宏的宏,可用于修改读取器解释代码的方式,即,您可以使用它们来使用#{和#}并使用诸如#(和#)之类的定界符。
这是我用于调试(在Clojure中)的一个:
user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil
我不得不处理C ++中的手动哈希表,其中该get
方法将非常量字符串引用作为参数,这意味着我不能使用文字来调用它。为了使处理起来更容易,我写了如下内容:
#define LET(name, value, body) \
do { \
string name(value); \
body; \
assert(name == value); \
} while (false)
尽管不太可能出现类似此问题的情况,但我发现您可以使用不对两次自变量进行两次求值的宏(例如通过引入真正的 let绑定)特别好。(承认,在这里我可以解决)。
我还求助于将东西包裹起来的丑陋骇客方式,do ... while (false)
这样您就可以在if的then部分中使用它,并且仍然可以按预期进行else部分的工作。您不需要Lisp,这是在语法树上操作的宏的功能,而不是字符串(或字符串,在C和C ++的情况下,我认为是令牌序列),然后进行解析。
有一些内置的线程宏可用于重组代码,使代码读起来更清晰(“线程”就像“将代码放在一起”,而不是并行性)。例如:
(->> (range 6) (filter even?) (map inc) (reduce *))
它采用第一种形式,(range 6)
并使其成为下一种形式的最后一个参数,进而使之成为下一种形式(filter even?)
的最后一个参数,依此类推,以便将上面的内容重写为
(reduce * (map inc (filter even? (range 6))))
我认为第一个读起来更清楚:“获取这些数据,对其进行处理,然后执行该操作,然后再进行其他操作,我们就完成了”,但这是主观的;客观上是正确的是,您按执行的顺序阅读操作(忽略懒惰)。
还有一个变体,它将前一个形式作为第一个(而不是最后一个)参数插入。一种用例是算术:
(-> 17 (- 2) (/ 3))
读为“取17,减去2并除以3”。
说到算术,您可以编写一个进行后缀符号解析的宏,这样您就可以说出例如,(infix (17 - 2) / 3)
并且它会吐出来(/ (- 17 2) 3)
,它的缺点是可读性差,并且是有效的Lisp表达式。这就是DSL /数据子语言部分。