Integrant入門(1) - Integrantの基本
実際のところはDuct使いたいから仕方なく覚えたい、とか、James Reevesが作ったから、とか、そのあたりが大きいのかもしれませんが…。何はともあれ、現時点で日本語による解説がないので簡単に説明しようと思います。 インストール
いつも通りプロジェクトの依存ライブラリとして追加します。現在の最新バージョンは0.6.1。
code:project.clj
語彙の説明
まずは簡単なコード例を提示しながら、Integrantで出てくる語彙を説明することにします。 code:clojure
(defmethod ig/init-key :default
x
x)
(ig/init {:my/x 1
:my/y 2
:my/z {:x (ig/ref :my/x)
:y (ig/ref :my/y)}})
;; :y 2,
;; :z {:x 1, :y 2}}
一旦注目してほしいのがinitという関数です。これはキーがキーワードのマップ(「設定マップ」)を受け取り、「システム」を構築します。
そして、マップのトップレベルにあるキーが、「コンポーネント」を指します。
なので、この設定では:my/x, :my/y, :my/zというコンポーネントが存在することになります。
そして、肝心な部分ですが:my/zコンポーネントは、refという関数を使うことによって、:my/x, :my/yコンポーネントに「依存する」ことを示しています。
ここまでで大雑把にIntegrantの説明を理解するために必要な語彙を説明しました。以下からは、これらの言葉を用いて説明していきます。 コンポーネントの初期化の基本
上の例でinit-keyというマルチメソッドに対して、ディスパッチ値を:defaultとしたメソッドを追加していました。
init-keyは各コンポーネントをどのように初期化/構築するかを決めるために利用します。今回の例だとすべてのコンポーネントは最初に受け取った値をそのままコンポーネントとすることになります。
ちょっとこれだけだと何が嬉しいのか分からないと思うので、もう少しだけ複雑な例にしてみましょう。
code:clojure
(defmethod ig/init-key :my/x
x
(* x 10))
(defmethod ig/init-key :my/y
y
(+ y 5))
(defmethod ig/init-key :my/z
(- x y))
(ig/init {:my/x 1
:my/y 2
:my/z {:x (ig/ref :my/x)
:y (ig/ref :my/y)}})
これはひとつ前の例と異なり、各コンポーネントの初期化方法を指定しています。
init-keyは第1引数としてコンポーネントのキーを受け取ります。なので、:my/xというキーが設定マップの中にあれば、:my/xを処理することができるinit-keyのメソッドを探してきて初期化しようとします。
これはマルチメソッドの仕組みにのっかるので、deriveなどを使ってヒエラルキーを作っていれば、直截的に:my/xをディスパッチ値に対するメソッドが追加されてなくても良いですし、先程のように:defaultを指定しておけばどこにもひっかからないコンポーネントは:defaultに対応するメソッドで処理されることになります(なので、詳しくはマルチメソッドについて調べてみてください)。
第2引数はそのコンポーネントを初期化するための値ですが、これは設定マップの各キーに対応するバリューになります。そのため、:my/xを初期化するときには1が、:my/yを初期化するときには2が渡されます。
そして、ここが重要なのですが、:my/zコンポーネントには{:x (ig/ref :my/x) :y (ig/ref :my/y)}というマップが設定されていたわけですが、:my/zの初期化をするとき(init-key)に渡されていたのは{:x 10, :y 7}です。
つまり、:my/zには初期化済みの:my/x, :my/yコンポーネントが渡された、ということです。前述した通り他のコンポーネントに依存するためにはref関数を利用し、そうすることでそのコンポーネントは初期化時に、初期化済みの依存したコンポーネント受け取ることができるようになる、というわけです。
このようにIntegrantは依存しているコンポーネント同士を適切な順序で初期化していくことができるので、これを初期化した後にあれを初期化しなければいけない、というような依存関係のあるもの同士を紐付けるのにも便利だったりします。