Clojureのslurp/spit はFile、URI、URL、Socket、I/O Stream、Reader/Writerなどを対象に入出力を行う多相的な関数だが、java7で追加された java.nio.file.Pathは対象に入っていない。
ということでPathも入出力対象として扱えるようにするためのメモ。
clojure.java.ioとIOFactory
slurp/spit はJVMホストなClojure固有関数(ClojureScript側には存在しない)であり内部的にはclojure.java.ioの関数 input-stream、output-stream、reader、writerがベースとなっている。これらの関数には前述の多相性がありその恩恵をslurp/spitも受けているといった具合になっている。
この多相性は対象型にプロトコル IOFactory を実装することで実現されており、以下のようにextenders関数で IOFactory を実装している型一覧を確認することができる。
(extenders clojure.java.io/IOFactory)
;;=> (nil java.io.InputStream java.net.URI java.io.Writer java.net.Socket java.io.Reader java.io.BufferedWriter java.io.BufferedOutputStream java.io.File java.io.OutputStream java.io.BufferedReader [C java.io.BufferedInputStream java.net.URL java.lang.String [B java.lang.Object)
java.nio.file.PathにIOFactoryを実装
extend関数に対象の型、プロトコル、実装する関数名と関数のマップを指定する。
(require '[clojure.java.io :as jio])
(extend java.nio.file.Path
jio/IOFactory
{:make-reader (fn [p opts] (jio/make-reader (jio/make-input-stream p opts) opts))
:make-writer (fn [p opts] (jio/make-writer (jio/make-output-stream p opts) opts))
:make-input-stream (fn [p opts] (jio/make-input-stream (.toFile p) opts))
:make-output-stream (fn [p opts] (jio/make-output-stream (.toFile p) opts))})
これでPathに対しても読み書きができるようになる。
または clojure.java.io/default-streams-implに以下のようなデフォルトの関数名と関数のマップが用意されており、これをベースにmake-input-stream
と make-output-stream
だけを実装する事もできる。
(require '[clojure.java.io :as jio])
(extend java.nio.file.Path
jio/IOFactory
(assoc jio/default-streams-impl
:make-input-stream (fn [p opts] (jio/make-input-stream (.toFile p) opts))
:make-output-stream (fn [p opts] (jio/make-output-stream (.toFile p) opts))))
プロトコル実装が衝突した場合の振る舞い
最後に少し本件から脱線するが、もし複数コードから同一の型とプロトコルに対して異なる実装を重複して行った場合にどのような振る舞いになるのかを確認してみた。
結果はエラーや警告は出ずに後勝ち。
あまり無いケースだと思うが、
- ライブラリ間でextendは衝突しうる
- 衝突したら後勝ち
- 衝突時にエラー、警告メッセージは表示されない
という点を心に留めておいた方が良さそうだ。