Clojure宏:什么时候函数不能复制宏的行为?

时间:2018-06-10 01:57:16

标签: clojure macros

我正在玩clojure宏,我发现很多宏行为我可以用函数组合进行复制。

一个很好的例子是线程宏:

(defn pipe [& fns]
  (reduce (fn [f g] (fn [arg] (g(f arg)))) fns))

(def pipeline
  (pipe
   #(+ % 1)
   #(* % 10)))

我可以使用像管道这样的更高阶函数轻松复制这个:

todo

必定存在无法用函数替换宏的情况。我想知道是否有人有这些情况的好例子,以及所涉及的重复主题。

5 个答案:

答案 0 :(得分:7)

宏的一个重要优点是它们能够在编译时转换代码而无需评估任何代码。宏在编译期间接收代码作为数据,但函数在运行时接收。宏允许您在某种意义上扩展编译器。

例如,Clojure的Renderer rend = someGameObject.AddComponent<Mesh renderer>(); and被实现为递归宏,扩展为嵌套的or形式。这允许对if / and的内部形式进行惰性评估,即如果第一个or形式是真实的,则将返回其值,并且不会评估其他任何形式。如果您将or / and作为函数编写,则在对其进行检查之前,将对其所有参数进行求值。

短路控制流程在or函数示例中不是问题,但与pipe相比,pipe会增加相当大的运行时复杂度,而->只会展开到嵌套表单。尝试作为函数实现的更有趣的宏可能是some->

  

我发现很多宏观行为我只能用功能组合复制

如果您的函数适合它,您当然可以用comp替换一个函数组成的简单线程宏,类似于&#34;无点&#34;其他函数语言中的样式:#(-> % inc str)在功能上等同于(comp str inc)#(str (inc %))

通常建议在可能的情况下更喜欢功能,即使在编写宏时,您通常也可以将大部分的工作放在一起工作&#34;功能。

答案 1 :(得分:2)

我学到的第一个宏是一个不能写成普通函数的宏的好例子:

(defmacro infix [[arg1 f arg2]]
  (list f arg1 arg2))

(infix (1 + 2))
=> 3

当然,这个精确的宏永远不会在野外使用,但它为更有用的宏设置了舞台,这些宏充当可读性助手。还应该注意,虽然可以使用普通函数复制许多基本宏的行为,但应该你呢?很难说你的管道示例比as->更容易读/写代码。

您正在寻找的“重复发生的主题”是您在编译时操作数据的情况(“数据”是代码本身),而不是运行时。任何需要函数使其参数未被评估的情况必须是宏。在某些情况下,您可以部分“欺骗”,只需将代码包装在函数中以延迟评估,但这并不适用于所有情况(如infix示例)。

答案 2 :(得分:1)

宏做的两件大事就是控制参数的评估并在编译时转换代码。你可以通过要求调用代码引用它们的参数来完成两个函数。

例如,您可以编写以这种方式调用的defn版本:

(defn 'name '[arg1 arg2]
  '(expression1)
  '(expression2)
  'etc)

然后,您可以随意eval参数,评估或不评估,更改执行顺序,或在评估表单之前更改表单,确切地说宏是有益的。

如果没有调用代码的任何合作,那些功能无法实现的宏可以获得这种能力。用户可以像调用普通函数一样调用宏,也不必以不同的方式处理它们的参数。

这就是宏如何允许你扩展语言:你不必像常规代码那样处理宏代码,不像JavaScript,Ruby或Python,只能用新的控制流扩展语言通过执行您在示例中所做的事情来构造,将代码包装在块,lambda或函数中。

答案 3 :(得分:1)

宏不能与函数互换,但您的示例是:

(macroexpand '#(+ % 1))
; ==> (fn* [p1__1#] (+ p1__1# 1))

它起作用的原因是因为参数需要一个函数而你使用一个成为函数的宏。但我知道cond是一个宏。它不能被函数实现替换,因为函数的参数被评估,cond的整点只是基于对谓词的评估,以特定的顺序评估参数的某些部分。例如。使用永远不会终止的递归函数,因为在评估函数cond的主体之前也将始终调用默认情况。

宏的全部意义在于扩展lamguage,并且由于评估由结果控制,因此除了将所有参数作为函数传递以延迟评估之外,您可以创建使用函数无法实现的各种新功能。

答案 4 :(得分:1)

在任何语言中,宏 - 从代码到代码的编译时功能 - 让你做三件事:

  1. 定义新的绑定表单(例如Clojure的解构let)。
  2. 更改评估顺序(例如orand)。
  3. 提供特定于域的语言(例如Instaparse)。
  4. 你可以争论3 - 是否真正需要宏实现DSL。当然,您可以执行从文本文件到文本文件的函数的代码生成器。或者,确定您可以执行Ruby样式的运行时DSL。但是如果你想要一个在编译时集成到编译器中的DSL,那么宏实际上就是你的“编译器API”。

    话虽如此,仅将宏用于这些特殊目的是有意义的。尽可能使用函数和/或数据驱动的代码。包括在宏观上提供“外观”背后的工作。