On Lisp風だとこんな感じでdispatchが説明されている。
;; conditional version
(defn behave [animal]
(cond
(= animal 'dog) (do '(wag-tail) '(bark))
(= animal 'rat) (do '(scurry) '(squeek))
(= animal 'cat) (do '(rub-legs) '(scratch-carpet))))
clojureっぽいdispatchをどうするかが説明されている。具体的にはprotocolを使ってやる。protocolは異なるデータ型に対して適用できる関数のリストみたいなものである。protocolは実際にはdeftype
かdefrecord
されたようなものに対して使うことができる。
;; Protocols. Define the protocol
(defprotocol animal
(behave [this] ))
;; define a dog
(defrecord dog [breed] )
;; add the animal protocol to dog type
(extend dog
animal
{:behave (fn [src] (do '(wag-tail) '(bark)))})
;; create a dog
(def my-dog (dog. "collie"))
;; see what it does
(behave my-dog)
;; define a rat
(deftype rat [color])
;; add the animal protocol to the rat type
(extend rat
animal
{:behave (fn [src] (do '(scurry) '(squeek)))})
;; create a rat
(def brown-rat (rat. "brown") )
;; see what it does
(behave brown-rat)
(extend String
animal
{:behave (fn [src] (do '(what)))})
(behave "huh")
もう一つ方法があって、それはmultimethodを使う方法。"While protocols are similar to overriding functions, multimethods are like overloading. Protocols broadened overriding to make polymorphism work independent of class hierarchies. Multimethods allow overloading based not just on parameter type, but even parameter values."ということらしい。
multimethodsのほうはパラメータの型だけじゃなくって、パラメータの値によってもdispatchの仕方を変更できる。そういうことなので、下では偶数と奇数で振舞を変更できるようなものを作っている。ネストしまくったりしなくてよいので、見通しがよくなりそうな感じですね。
;; Multimethods. Define the multimethod type
(defmulti behave-multi identity)
;; define implementations for our animals
(defmethod behave-multi 'dog [x]
(do '(wag-tail) '(bark)))
(defmethod behave-multi 'rat [x]
do '(scurry) '(squeek))
;; try them out
(behave-multi 'dog)
(behave-multi 'rat)
;; You can dispatch on parameter values,
;; not just parameter types
(defmulti two-behaviors
(fn [num] (if (odd? num) :odd :even)))
(defmethod two-behaviors :odd [num]
(str num " is odd"))
(defmethod two-behaviors :even [num]
(str num " is even"))
(two-behaviors 3)
(two-behaviors 4)
(let [y 7]
(defn scope-test [x]
(list x y)))
(let [y 5]
(scope-test 3)) ; (3 7)
(defn list+ [lst n]
(map (fn [x] (+ x n))
lst))
(list+ '(1 2 3) 10) ; (11 12 13)
;; lexically scoped counter
(let [counter (atom 0)]
(defn new-id [] (swap! counter + 1))
(defn reset-id [] (reset! counter 0 )))
(new-id) ; 1
(reset-id) ; 0
;; adder fuctions
(defn make-adder [n]
(fn [x] (+ x n)))
(def add2 (make-adder 2))
(def add10 (make-adder 10))
(add2 5) ; 7
(add10 3) ; 13
;; adder that allows changing the added amount
(defn make-adderb [n]
(let [amt (atom n)]
(fn [x & change ]
(if change (reset! amt x ))
(+ x @amt))))
(def addx (make-adderb 1))
(addx 3) ; 4
(addx 100 true) ; 200
(addx 3) ; 103
;; First dbms version
(defn make-dbms [db]
(list
(fn [key]
(db key))
(fn [key val]
(assoc db key val))
(fn [key]
(dissoc db key))))
(def cities (make-dbms {'boston 'us, 'paris 'france}) )
((first cities) 'boston) ; us
((second cities) 'london 'england) ; {boston us, london england, paris france}
((last cities) 'boston) ; {paris france}
;; Mutable dbms example
(defn make-mutable-dbms [db]
(let [mdb (atom db)]
(list
(fn [key]
(@mdb key))
(fn [key val]
(swap! mdb assoc key val))
(fn [key]
(swap! mdb dissoc key)))))
(def citiesx (make-mutable-dbms
{'boston 'us, 'paris 'france}))
((first citiesx) 'boston) ; us
((second citiesx) 'london 'england) ; {boston us, london england, paris france}
((last citiesx) 'boston) ; {london england, paris france}
;; dbms with commands stored in a
;; map instead of a list
(defn make-dbms-map [db]
(let [mdb (atom db)]
{:select (fn [key] (@mdb key))
:insert (fn [key val]
(swap! mdb assoc key val))
:delete (fn [key]
(swap! mdb dissoc key))}))
(def citiesm (make-dbms-map
{'boston 'us 'paris 'france}))
((:select citiesm) 'boston) ; us
((:insert citiesm) 'london 'england) ; {boston us, london england, paris france}
((:delete citiesm) 'boston) ; {london england, paris france}