Last active
August 29, 2015 14:24
-
-
Save oubiwann/481ca54466e7acc31131 to your computer and use it in GitHub Desktop.
Dispatch in LFE and Clojure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;;;; From the Clojure Cookbook | |
| ;;;; by Luke VanderHart and Ryan Neufeld | |
| ;; The easiest way to implement runtime polymorphism is via hand-rolled, | |
| ;; map-based dispatch using functions like cond or condp: | |
| (defn area | |
| "Calculate the area of a shape" | |
| [shape] | |
| (condp = (:type shape) | |
| :triangle (* (:base shape) (:height shape) (/ 1 2)) | |
| :rectangle (* (:length shape) (:width shape)))) | |
| (area {:type :triangle :base 2 :height 4}) ;; -> 4N | |
| (area {:type :rectangle :length 2 :width 4}) ;; -> 8N | |
| ;; This approach is a little raw, though: area ties together dispatch and | |
| ;; multiple shapes’ area implementations, all under one function. Use the | |
| ;; defmulti and defmethod macros to define a multimethod, which will | |
| ;; separate dispatch from implementation and introduce a measure of | |
| ;; extensibility: | |
| (defmulti area | |
| "Calculate the area of a shape" | |
| :type) | |
| (defmethod area :rectangle [shape] | |
| (* (:length shape) (:width shape))) | |
| (area {:type :rectangle :length 2 :width 4}) ;; -> 8 | |
| ;; Trying to get the area of a new shape... | |
| (area {:type :circle :radius 1}) | |
| ;; -> IllegalArgumentException No method in multimethod 'area' for | |
| ;; dispatch value: :circle ... | |
| (defmethod area :circle [shape] | |
| (* (. Math PI) (:radius shape) (:radius shape))) | |
| (area {:type :circle :radius 1}) ;; -> 3.14159 ... | |
| ;; Better, but things start to fall apart if you want to add new geometric | |
| ;; functions like perimeter. With multimethods you’ll need to repeat dispatch | |
| ;; logic for each function and write a combinatorial explosion of | |
| ;; implementations to suit. It would be better if these functions and their | |
| ;; implementations could be grouped and written together. Use Clojure’s protocol | |
| ;; facilities to define a protocol interface and extend it with concrete | |
| ;; implementations: | |
| ;; Define the "shape" of a Shape object | |
| (defprotocol Shape | |
| (area [s] "Calculate the area of a shape") | |
| (perimeter [s] "Calculate the perimeter of a shape")) | |
| ;; Define a concrete Shape, the Rectangle | |
| (defrecord Rectangle [length width] | |
| Shape | |
| (area [this] (* length width)) | |
| (perimeter [this] (+ (* 2 length) | |
| (* 2 width)))) | |
| (-> Rectangle 2 4) ;; -> #user.Rectangle{: length 2, :width 4} | |
| (area (-> Rectangle 2 4)) ;; -> 8 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (defun area | |
| ((`(#(type triangle) #(base ,b) #(height ,h))) | |
| (* b h (/ 1 2))) | |
| ((`(#(type rectangle) #(length ,l) #(width ,w))) | |
| (* l w))) | |
| (area '(#(type triangle) #(base 2) #(height 4))) | |
| (area '(#(type rectangle) #(length 2) #(width 4))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment