この記事を読んでなるほどな〜と思ったので記事にしてみる。
依存性逆転とは
雑にいうと実装ではなくインターフェイスに依存させ、モジュール間の依存関係を疎結合にする手法。英語ではDependency-Inversion Principleと呼ばれ、頭文字をとってDIPとすることが多い。
ElmではDIPをどう表現するか
一般的な静的型付け言語ではインターフェイス相当の言語機能が提供されている。たとえばScalaだとtraitだし、Javaだとinterfaceあたり。しかしElmにはそれらがない。
そこで、Elmでは型エイリアスを使ってインターフェイスっぽい表現をする
type alias Score = Int type alias PersistScore msg = Score -> Cmd msg
上のPersistScore
がインターフェイス相当になっている。Score
を受け取り、なんらかの手段で永続化を行うCmd
型に変換するインターフェイスである。
もう少し詳しく
ellieで用意されているTODOアプリを例にする。実際のコードのリンクは以下。
https://ellie-app.com/7Z52XPNmbfca1
Increment
とDecrement
に加えて、データの永続化を行うSave
というメッセージが新しく定義されている。Save
メッセージを受け取ると、何らかの方法で現在のcountの永続化を実行する。
type Msg = Increment | Decrement | Save type alias Persist msg = Int -> Cmd msg update : Persist msg -> Msg -> Model -> ( Model, Cmd msg ) update persist msg model = case msg of Increment -> ( { model | count = model.count + 1 }, Cmd.none ) Decrement -> ( { model | count = model.count - 1 }, Cmd.none ) Save -> ( model, persist model.count )
update
関数は第一引数に永続化のためのアダプタを受け取るため、main関数での繋ぎこみの際に実装を差し込む。ここでは実装はlocalStorageへの永続化を行うlocalStoragePersister
になる。
port localStoragePersister : Int -> Cmd msg main : Program () Model Msg main = Browser.element { init = init , view = view , update = update localStoragePersister , subscriptions = \_ -> Sub.none }
この実装はupdate
関数がPortに依存しないため、テストの際に以下のようなモックの関数を渡してupdate
関数内部でのPort呼び出しの挙動などをテストできるようになる。Portに依存しなくなることでピュア度が増すし、テストもしやすくなる。
-- Portの実装をモックする関数 mockPersister : Int -> Cmd msg mockPersister _ = Cmd.none
このようなPortの抽象化は、Portをたくさん使うプロダクトになればなるほど、意外に必要な場面が出てくる。
完全な抽象化は難しい
しかし、この抽象化はあまり完全なものであるとは言えない。
たとえば、実際にありえるかは分からないが「開発環境ではLocalStorageへ永続化するが、本番環境ではWebAPIへ永続化する」のような機能を抽象化するのは少し無理がある。
そもそも、PortはElmランタイムに処理を投げて終わりであるのに対して、HTTPリクエストの場合にはTaskの結果としてResult型を受け取る必要がある。 メッセージとしてリクエストの結果を受け取らないことが許されてないため、そもそもPortとTaskでモノが違うだろというハナシになってくる。 PortとのデータのやりとりがTaskで行えるようになればちょっとは可能性が出てくるが、あまり可能性はないと考えてよいだろう。
- 作者:マーチン・ファウラー
- 出版社/メーカー: 翔泳社
- 発売日: 2016/02/19
- メディア: Kindle版