Haskellの代数データ型をJava的なインタフェースと捉える
Haskellの代数データ型は僕にとってJavaのインタフェースに近い。
データ型がインタフェースでそのデータを受け取る関数がインタフェースのメソッドに相当する。 データをパターンマッチで分解して値コンストラクタ別の関数定義をするのは、 インタフェースに対する実装を与えているものだと考えている。
data Maybe a mapMaybe :: (a -> b) -> Maybe a -> Maybe b
Maybe a
というデータ型があるとする。これがインターフェース。
mapMaybe
という関数があるとする。これがメソッド。
インタフェースに実装を与えていく。
まずはデータ構造としてのインタフェース実装を与える。
data Maybe a = Just a | Nothing mapMaybe :: (a -> b) -> Maybe a -> Maybe b
ここでは Just a
と Nothing
という2種類の実装が Maybe a
というインタフェースに対して与えられた。
次に関数としてのインタフェース実装を与える。
data Maybe a = Just a | Nothing mapMaybe :: (a -> b) -> Maybe a -> Maybe b mapMaybe f (Just x) = Just (f x) mapMaybe f Nothing = Nothing
mapMaybe
の具体的な定義が各インタフェース実装ごとに記述できた。
mapMaybe
のユーザから見れば、 Maybe a
の実態が Just a
なのか Nothing
なのかは意識する必要がない。
これが抽象化されているということ。
オブジェクト指向言語ではvirtualmethodなどを定義して、実行時に実装へと ディスパッチ する。 それと同じように上記の例ではパターンマッチを使って値コンストラクタを判定し、 ディスパッチ している。
この考え方で割りと上手くいく。ライブラリでも作らない限りはこの考え方で多くのことが抽象化できる。 しかしユーザが拡張可能なインタフェースを定義しようと思うと、問題もあったりする。
Maybe a
の値コンストラクタを増やした、つまり Maybe a
インタフェースに新しい実装を与えたときに、
Maybe a
にパターンマッチをかけている関数も修正しなければならくなるし、再コンパイルが必要になってしまう、という問題。
これを解くために型クラスを使ったインタフェースの定義をしていくことになる。
で、tagless finalってなんだろうというところが今。
追記
先日「型クラスはインタフェースなのか否か」みたいな議論があった。 型クラスを正しく捉えようという目線からは、「インタフェースじゃない」という意見があった。 型クラスをファーストステップとしてアナロジーで理解しようという目線からは、「インタフェースみたいなもの」という意見があった。
視点が違えば違う言い方になるよな、というだけなのでどちらでもいい。
僕は fumieval さんの記事にあるように 「性質の表現」くらいにしか考えていないので、あんまりアナロジーとして見ていない。
といいながらこの記事ではADTとインタフェースのアナロジーを語っているところに何か破綻があるような気もするけれど。
Clojureでの副作用の表現について
自分が悩んでることを拙い語彙でぽつぽつ綴るので、誰にも伝わらないと思う。
DBアクセスする機能を実装したいとする。
java.jdbc
を使ってDBアクセスレイヤを実装することになるのが普通かなと思う。
Clojureは関数型プログラミング言語なので、組み立てに使う部品(関数)はできるだけ純粋な方がいい。
DBからのデータ取得やデータ更新を行う手続きもできるだけ副作用のない関数で組み立てたい。
DBアクセスする手続きなのにそんなことできるのか? と思う。
Haskellのように独自ASTとinterpreterのパターンならそれができる。 Clojureでも同じような構成になるのだろうか。
いくつかアプローチがありそうだ。
- 副作用を起こす関数は高階関数で受け取る
- 副作用を起こすコマンドを生成する純粋関数を合成し、interpreterで副作用を発行する
基本的には高階関数で解けるのが一番低コストでよいと思う。 高階関数を使ったDIだと捉えるとわかりやすい。
ただ適用する関数が色々な種類の副作用関数を要求するようになると突然複雑になる。DIで言えば注入する依存が肥大化している状態。 気軽に依存が満たせなくなるので、とても腰の重いインタフェースができる。
それを補うために関数の辞書を渡すことになってしまう。しかしインタフェースが抱える複雑さは変わらないので、実はあまり解決になってない。
2案
あるパラメータを渡すと「印字せよ」とか「INSERT文を発行せよ」という命令を返す関数を中心に組み合わせる。 命令は実行されるまで何の副作用も生まない。なので、その関数を呼び出しても、何か副作用が起きるわけではない。 実行すれば副作用が起こる命令が手元にあるだけだ。
これはDIとは完全に切れ離された、より純粋な設計になる。 しかし一方で、命令を実行する別の機能が必要になるため複雑さはそちらになお宿っている。
が、ビジネスドメインの記述に徹することができるのは保守上のメリットがある。
対して、命令と実行を受け持つ関数はなかなか軽くもシンプルにもならないだろうと思う。
それでもドメインを純粋に保てるのなら2案になるのかな。
設計プランは後ほど追記 - 4/23 まだできてない Clojureで遊んでいる場合ではない感じに。。。