开发者

Declaring design pattern in a Macro in Clojure

One of the virtues of Lisp is Macros. I have been reading a lot that in Java you write design patterns again and again. Not in Lisp/Clojure.

In Li开发者_Go百科sp/Clojure you’d declare the pattern in a macro and you’d only have to write the actual code.

Ok, nice and dandy but seeing is believing.

Would you please provide me (or refer me) an example with code -preferably Clojure- about how to declare a design pattern in a Macro?


Most of the existing design patterns originated and makes sense only in the Object Oriented world. As soon as you step into functional programming, and maybe especially Lisp dialects such as Clojure, your need for design patters gets smaller and smaller. There's an intresting discussion about design patterns and FP here.

On the other hand, macros are not intended to encapsulate design patterns, but rather extend the language with constructs that are more handy to solve the problem at hand. Take the with-open macro: calling it a design pattern to invoke close on a resource seems simply wrong.

Patterns exist in the FP world as well, but as you don't have objects anymore their main focus is on algorithms. Good examples of "patterns" for FP languages are monads and zippers.

Warning: it might take time to grok those concepts, but it's definitely worth to understand every bit of them.


Example of a typical macro usage to implement a design pattern would be the "Decorator" pattern applied to an existing function.

; 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

Note: you don't really need a macro to do this, you could also do it with a higher order function.


Its not about macros, its about functional programming. Macros can make stuff look and feel better, but it's all about the functions. If you write in FP you won't encounter many idioms that you have to repeat all the time (at least none that you can't make better with more functions)

Take the strategy-pattern. If your language has lambdas you don't need it any more.

Often you may be using the same pattern but your language makes it so easy to express it that you would never call it Implementing a Design Pattern. It's just Programming.


All good answers so far. I would like to amplify that the key practical difference between a macro and a function is that a macro can control when and if its arguments are evaluated, and a function cannot. Among other implications, this allows you to write new control constructs in the language, which you can't do with functions.

For example, take a look at the if-not macro from Clojure core. This is exactly like the standard "if" but inverted: if the condition is false, it runs the first bit of code; if the condition is true, it runs the second.

If you write something similar as a function, it won't work. The function version will run both the "then" and the "else" code immediately when the function is called, regardless of whether the condition is true or false. The macro version, on the other hand, can choose whether (and when) to run the code it's given as arguments.

A less trivial example is abstracting out the typical "try ... catch ... finally" control construct ubiquitous in Java and common in other languages into a "with-whatever" macro. If you're holding a finite resource such as a file handle or a network socket that must be released, even in case of error, you need a "finally" block to do that, but your application code has to be spliced unevaluated into the interior of the "try" block, to be run within that context.

You wind up copying essentially the same unaltered boilerplate try...catch...finally block around everywhere in your program and pasting a small locally applicable section into the "try" part. (Look at any non-trivial Java source code.) This boilerplate can't be abstracted into a function, because a function would evaluate the local code immediately when it's called, in the caller's context, and then give the result of that code to the "with-whatever" function.

A macro, on the other hand, can delay evaluation until specifically called for by the macro's code, allowing it to splice your arbitrary local code into the interior of the "try" construct unevaluated. The entire result (the entire try/catch/finally, including your unevaluated app-specific code within the try) is then returned to the caller and spliced into the caller's context, complete, to be executed there.

You can thus write a program that writes programs.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜