2013年2月7日木曜日

Destructuringを使おう

Clojureで積極的にDestructuringを使っていこうという話です。

ListやVectorに対する再帰


例: index-of

(defn index-of [value coll]
(loop [index 0 coll coll]
(cond (empty? coll) nil
(= value (first coll)) index
:else (recur (inc index) (rest coll)))))
view raw index_of1.clj hosted with ❤ by GitHub

Destructuringを使うとこんな感じで書ける。

(defn index-of' [value coll]
(loop [index 0 [head & tail :as coll] coll]
(cond (empty? coll) nil
(= value head) index
:else (recur (inc index) tail))))
view raw index_of2.clj hosted with ❤ by GitHub

firstとrestがなくなりました。

例: end

(defn end [coll]
(if (empty? (rest coll))
(first coll)
(recur (rest coll))))
view raw end1.clj hosted with ❤ by GitHub

Destructuringは関数の引数部に直接書ける。

(defn end' [[head & tail]]
(if (empty? tail)
head
(recur tail)))
view raw end2.clj hosted with ❤ by GitHub

リスト全体を束縛する変数を省略出来ました。

MapやRecordに対するDestructuring


レコードに対する操作

(defrecord Complex [real imaginary])
view raw complex.clj hosted with ❤ by GitHub

例: abs

(defn abs [complex]
(Math/sqrt (+ (Math/pow (:real complex) 2)
(Math/pow (:imaginary complex) 2))))
view raw abs1.clj hosted with ❤ by GitHub

Destructuringを使うとこんな感じ。

(defn abs' [{:keys [real imaginary]}]
(Math/sqrt (+ (Math/pow real 2)
(Math/pow imaginary 2))))
view raw abs2.clj hosted with ❤ by GitHub

{:keys [foo bar ...]}はフィールドを列挙することでその値を束縛します。

例: add

(defn add [complex complex']
(assoc complex
:real (+ (:real complex) (:real complex'))
:imaginary (+ (:imaginary complex) (:imaginary complex'))))
view raw add1.clj hosted with ❤ by GitHub

フィールドと違った名前の変数名を付ける場合は{foo' :foo bar' :bar ...}という記法が使えます。

(defn add' [{:keys [real imaginary] :as complex}
{real' :real imaginary' :imaginary}]
(assoc complex
:real (+ real real')
:imaginary (+ imaginary imaginary')))
view raw add2.clj hosted with ❤ by GitHub

Destructuringを使うことで要素の取得、束縛を簡素にしませんか?

2013年2月2日土曜日

Clojureで継承みたいなナニカ

Clojureのdeftypeやdefrecordではinterfaceのみを実装することができます。
しかし、継承のように実装があるclassを継承したいときがあります。

例えばこんなコードがあります。

(defprotocol Sequence
(add [xs x])
(empty [xs])
(head [xs])
(tail [xs]))
(defrecord Cons [x xs]
Sequence
(add [xs x] (Cons. x xs))
(empty [xs] (Nil.))
(head [_] x)
(tail [_] xs))
(defrecord Nil []
Sequence
(add [xs x] (Cons. x xs))
(empty [xs] (Nil.))
(head [_] nil)
(tail [xs] xs))
(defrecord Wrapped [list]
Sequence
(add [xs x]
(update-in xs [:list] (partial cons x)))
(empty [xs]
(Wrapped. '()))
(head [xs]
(first list))
(tail [xs]
(update-in xs [:list] rest)))
view raw sequence.clj hosted with ❤ by GitHub

ConsとNilのaddとemptyの実装が同じです。
共通化したい。

とりあえず、addとemptyを別Protocolに。

(defprotocol Sequence
(head [xs])
(tail [xs]))
(defprotocol Adder
(add [xs x])
(empty [xs]))
view raw adder.clj hosted with ❤ by GitHub

ListというProtocolを作って、それに対するAdderを定義します。

(defprotocol List
(list [xs]))
(extend-type List
Adder
(add [xs x] (Cons. x xs))
(empty [xs] (Nil.)))
view raw list.clj hosted with ❤ by GitHub

ConsとNilに対してListを実装すれば、めでたしめでたし。

(defrecord Cons [x xs]
List
(list [xs] xs)
Sequence
(head [_] x)
(tail [_] xs))
(defrecord Nil []
List
(list [xs] xs)
Sequence
(head [_] nil)
(tail [xs] xs))
view raw cons.clj hosted with ❤ by GitHub