Clojureのdefprotocolについて調べてみた。Javaでいうインターフェースのようなものなんだけど、Clojureは動的型の言語なので、インターフェース(のようなもの)があることで、何がよいのか、というのが今回のテーマです。
まずは使ってみるところから。
Protoclの定義
(defprotocol Hoge
(foo [this])
(bar [this a b]))
これで、defprotocolを呼び出したネームスペースにfooという関数とbarという関数が定義されます。(ns-internsで確認できます。)
protocolの実装
このプロトコルに対する実装を与えるコードは以下のようになります。
(defrecord Hoge1 []
Hoge
(foo [this] "this is Hoge 1")
(bar [this a b] (str a b)))
(defrecord Hoge2 []
Hoge
(foo [this] "this is Hoge 2")
(bar [this a b] (str b a)))
関数を実行すると以下のようになります。
(foo (Hoge1.))
=> "this is Hoge1"
(foo (Hoge2.))
"this is Hoge2"
(bar (Hoge1.) "a" "b")
=> "ab"
(bar (Hoge2.) "a" "b")
=> "ba"
プロトコルを通じて、fooやbarという同一のネームスペース・名前の関数に多態性(第一引数のインスタンスによって関数の振る舞いが変わる)が与えられたのがわかると思います。
protocolの作成単位
protocolは標準実装を定義することはできないので、一つのプロトコルに沢山の関数を用意すると、レコードを一つ作るたびに実装を行うコードを書かなければいけません。
このため、1つのプロトコルに用意する関数は少なくしたほうが、分かり切った実装を何度も書かなくて済むようになります。