在Clojure中声明宏中的设计模式

Declaring design pattern in a Macro in Clojure

Lisp的优点之一是宏。我一直在阅读很多,在Java中,你一次又一次地编写设计模式。不在Lisp/Clojure中。在Lisp/Clojure中,您将在宏中声明模式,并且只需编写实际的代码。

好吧,漂亮漂亮,但眼见为实。

请给我(或推荐我)一个代码示例——最好是Clojure——关于如何在宏中声明设计模式?


大多数现有的设计模式起源于面向对象的世界,并且仅在面向对象的世界中才有意义。一旦你开始了函数式编程,特别是Lisp方言,比如Clojure,你对设计模式的需求就会越来越小。这里有一个关于设计模式和FP的有趣的讨论。

另一方面,宏并不是用来封装设计模式,而是用更方便解决手头问题的构造来扩展语言。以with-open宏为例:将其称为对资源调用close的设计模式似乎是完全错误的。

模式也存在于FP世界中,但是由于您不再拥有对象,它们的主要焦点是算法。fp语言的"模式"很好的例子是monads和zippers。

警告:探索这些概念可能需要一些时间,但绝对值得理解其中的每一点。


实现设计模式的典型宏用法示例是应用于现有函数的"decorator"模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; a simple function
(defn square [x] (* x x))

; a macro to"decorate" a function with a debug output println
(defmacro with-debug-output [f]
  `(fn [& args#]
     (let [result# (apply ~f args#)]
       (println (str"Debug-output:" result#))
       result#)))


; call the straight function
(square 16)
=> 256

; call the decorated function
((with-debug-output square) 16)
Debug-output: 256
=> 256

注意:您不需要宏来执行此操作,也可以使用高阶函数来执行此操作。


它不是关于宏,而是关于函数式编程。宏可以使事物看起来和感觉更好,但它是关于函数的。如果用fp编写,就不会遇到许多必须一直重复的习语(至少没有一个你不能用更多的函数做得更好的习语)。

采取战略模式。如果你的语言有lambda,你就不再需要它了。

通常情况下,您可能使用相同的模式,但您的语言使其易于表达,以至于您永远不会称之为实现设计模式。只是编程而已。


到目前为止所有的好答案。我想进一步说明,宏和函数之间的关键实际区别在于,宏可以控制何时以及是否计算参数,而函数则不能。除其他含义外,这允许您用语言编写新的控件构造,而不能用函数编写。

例如,看看Clojure核心的if-not宏。这与标准的"if"完全相同,但却是颠倒的:如果条件为假,则运行代码的第一位;如果条件为真,则运行第二位。

如果你写了类似于函数的东西,它就不会工作。函数版本将在调用函数时立即运行"then"和"else"代码,不管条件是真是假。另一方面,宏版本可以选择是否(以及何时)运行作为参数提供的代码。

一个不那么简单的例子是抽象出典型的"尝试…抓住…最后,"控制结构在Java中无处不在,并在其他语言中通用成一个"具有任意"的宏。如果您持有有限的资源,例如必须释放的文件句柄或网络套接字,即使出现错误,也需要一个"finally"块来执行此操作,但必须将应用程序代码拼接到"try"块的内部,才能在该上下文中运行。

你最终复制了基本上相同的未修改的样板文件try…catch…最后在你的程序中的任何地方阻塞,并将一个小的本地适用部分粘贴到"try"部分。(查看任何非平凡的Java源代码)这个样板不能被抽象成一个函数,因为函数将在调用方的上下文中立即调用本地代码,然后将该代码的结果赋予"具有任意功能"的函数。

另一方面,宏可以延迟计算,直到宏的代码特别要求,允许它将任意的本地代码拼接到"try"构造的内部(未计算)。然后,整个结果(整个try/catch/finally,包括try中未计算的特定于应用程序的代码)返回给调用方,并拼接到调用方的上下文中,完成,在那里执行。

因此,您可以编写一个编写程序的程序。