TL;DR
- プロトコルは Expression Problem に対する Clojure の解法の 1 つで、インタフェースと同じく仕様だけを指定するが、既にある型に元のコードを触らずに追加できる。
defprotocolで定義し、extend、extend-type、extend-protocolで既存の型に実装を足す。
内容
Expression Problem に対する Clojure の解法の 1 つが、プロトコルである。
プロトコルはインタフェースと同様に、仕様だけを指定し、データ型が複数のプロトコルを実装できる。加えて、既にある型に対して、元のコードを触ることなくプロトコルを追加できる。
プロトコルの定義(defprotocol)
プロトコルは defprotocol で定義する。
(defprotocol name & opts+sigs)IOFactory は次のように定義できる。
(defprotocol IOFactory
"A protocol for things that can be read from and written to."
(make-reader [this] "Creates BufferedReader.")
(make-writer [this] "Creates BufferedWriter."))これを使って、java.io.InputStream と OutputStream が IOFactory プロトコルをサポートするように拡張していく。
extend 関数
extend を使うと、既存の型にプロトコルを追加し、必要な関数定義(メソッド)を書ける。
(extend type & proto+mmaps)引数には拡張する型の名前、プロトコル名、メソッド実装のマップを受け取る。メソッド実装のマップのキーはメソッド名をキーワードにしたものである。
InputStream に対する IOFactory の実装は次のようになる。
(extend InputStream
IOFactory
{:make-reader (fn [src] (-> src InputStreamReader. BufferedReader.))
:make-writer (fn [dst]
(throw (IllegalArgumentException.
"Can't open as a InputStream.")))})同様に、OutputStream に対しても IOFactory を実装できる。
(extend OutputStream
IOFactory
{:make-reader (fn [src]
(throw
(IllegalArgumentException.
"Can't open as an OutputStream.")))
:make-writer (fn [dst] (-> dst OutputStreamWriter. BufferedWriter.))})InputStream に書き込んだり、OutputStream から読み込んだりはできないので、これらのケースでは例外を投げるようにしている。
extend-type マクロ
java.io.File にも IOFactory プロトコルを追加する。今回は extend ではなく、extend-type マクロを使う。
(extend-type type & specs)引数として、拡張する型と複数の spec を渡す。各 spec はプロトコル名と必要なメソッド実装を並べたものである。
(extend-type File
IOFactory
(make-reader [src]
(make-reader (FileInputStream. src)))
(make-writer [dst]
(make-writer (FileOutputStream. dst))))この実装では InputStream(実際はそのサブクラスである FileInputStream)を作った後、make-reader を再帰的に呼び出している。この呼び出しは InputStream に定義したメソッドへとディスパッチされる。make-writer や後述するメソッド定義でも同様である。
extend-protocol マクロ
複数のデータ型に同じプロトコルを追加するには、extend-protocol マクロを使える。
(extend-protocol protocol & specs)引数には最初にプロトコルの名前を取り、複数の「型名とメソッド実装」を書く。
(extend-protocol IOFactory
Socket
(make-reader [src]
(make-reader (.getInputStream src)))
(make-writer [dst]
(make-writer (.getOutputStream dst)))
URL
(make-reader [src]
(if (= "file" (.getProtocol src))
(-> src .getPath FileInputStream.)
(.openStream src)))
(make-writer [dst]
(make-writer (if (= "file" (.getProtocol dst))
(-> dst .getPath FileOutputStream.)
(throw (IllegalArgumentException.
"Can't write to non-file URL"))))))