この Protocol の使い方正しいんだろうか?
Clojure に限らないんだが関数名決めるのってプログラミングで一番難しいとか 誰か偉い人も言ってた気がする。
んで、自分で作った関数に付けたい名前が clojure.core
にある関数名と被っている事があった場合どうするのが良いのか。
最も単純なのは被らない名前にしてしまう、だがそれも面白くないなとちょっと考えてたら そういや Protocols 使えるんじゃね?とふと思って試してみた。
name で使った例
name
ってのは引数で Symbol か Keyword を受け取ると文字列を返す関数。
(name 'a) "a" (name :b) "b"
これが例えば String と言う java.lang.Class を渡すとエラーになる。 java.lang.Class は未対応なので当然の話。
(name String) Execution error (ClassCastException) at~
これを Protocols で拡張してみる。
(defprotocol ExtendName (name [x])) (extend-protocol ExtendName Class (name [x] (.getName x)) Object (name [x] (clojure.core/name x)))
最後に Object に指定しておくとここが実質デフォルト実装になる。 公式 でも以下のように書かれてるのでこの書き方は別に問題なさそう。
To define a default implementation of protocol (for other than nil) just use Object
nil 以外と言う但し書きがあるけどそんな問題にならないかと思う。
これを評価すると既存の実装上書きするよと Warning 出るがそこはしょうがない。 再び実行してみると、
(name 'a) "a" (name :b) "b" (name String) "java.lang.String"
結果 java.lang.Class にも対応した name になった。
デフォルト実装の書き方はまだしも、core に定義されてるのを上書きするのは どうなんだろうかと言うのが疑問として残ってる。
name は単純だから元の処理を邪魔しない形になってるが、 拡張しようとする関数を良く理解してないと思わぬ所でハマりそうな気がしないでもないが どうなんだろう。
と、疑問を残しつつも思いついたネタだったので今回記事に上げてみた。
余談: Multimethods と Protocols どちらを使うべきか
自分は関数の引数の最初の型による dispatch で事足りるなら Protocols、 そうでないもっと複雑な dispatch をしたければ Multimethods と言う理解。
実際に比較検証した事はないが Protocols の方がパフォーマンスも良いらしい。
後者が必要になる場面と言うのは実際かなり少ないと思う。