Skip to content

Instantly share code, notes, and snippets.

@oubiwann
Last active August 29, 2015 14:24
Show Gist options
  • Select an option

  • Save oubiwann/481ca54466e7acc31131 to your computer and use it in GitHub Desktop.

Select an option

Save oubiwann/481ca54466e7acc31131 to your computer and use it in GitHub Desktop.
Dispatch in LFE and Clojure
;;;; 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
(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