Slide 1

Slide 1 text

Dotty で軽量な DI ライブラリを書いてみた @jooohn1234

Slide 2

Slide 2 text

Jun Tomioka @jooohn1234 Software Engineer at M3, Inc.

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Dotty で軽量な DI ライブラリを書いてみた ● つくったもの紹介 ● テクニック ○ Tuple ○ given ● その他 Dotty の機能 ● まとめと感想 ※今回試したバージョンは 0.26.0-RC1

Slide 5

Slide 5 text

謝罪 ● 細かい解説は完全に Rookies 向けじゃない内容 になってしまいました・・・。全てを理解する必要は ありません! ● 工夫すればコンパイル時にいろいろなことができ るんだな、と興味を持つきっかけになれば幸いで す!

Slide 6

Slide 6 text

Dotty とは ● ざっくりいうと Scala 3 のコンパイラ ○ https://dotty.epfl.ch/docs/index.html

Slide 7

Slide 7 text

つくったもの 紹介

Slide 8

Slide 8 text

https://github.com/jooohn/dotty-di-playground ※ライブラリとして公開する予定はありません

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

以下のような依存関係 Baz Bar Foo Int

Slide 11

Slide 11 text

● Design.of[Try] で Try を文脈として扱うことを宣言。このコンテナ作成は 失敗する可能性がある (Success | Failure) ○ 利用者がこの文脈を選べる (Future, Resource 等)。 ○ Tagless Final スタイル ● bind は コンテナに登録するインスタンスのファクトリを指定。ここでは単 に Foo, Bar のapply を指定。 ○ それぞれ Int => Foo, Foo => Bar が登録されている。 ● bindF は Try[Baz] を返すファクトリを登録。渡された Int が負数の場合 に失敗。

Slide 12

Slide 12 text

● この時点で依存を解決しようとしてもコンパイルエラーとなる。 ● Foo, Bar, Baz のファクトリは登録されているが、 Baz と Foo が依存して いる Int が登録されていないため。 Baz Bar Foo Int

Slide 13

Slide 13 text

● give ではファクトリではなく値を直接登録。 ● Baz の生成は、Int の値によって挙動が変わっている ○ 1 を渡した方はいい感じに Success している ○ -1 を渡した方は Failure している

Slide 14

Slide 14 text

特徴 ● 機能 ○ コンパイル時に依存の充足をチェック ■ 必要な依存が未登録の場合にコンパイルエラー ○ 任意の文脈 (Try, Future 等) で実行可能 ■ 非同期処理を挟んだり、リソース管理したりできる ● コード ○ core 部分 100行未満のDIコンテナ ■ (一般的な型クラス・ADTの自前実装を除く) ○ 依存は標準ライブラリのみ ○ マクロ未使用 ■ compiletime package も未使用

Slide 15

Slide 15 text

テクニック

Slide 16

Slide 16 text

Tuple

Slide 17

Slide 17 text

Tuple ● Dotty の Tuple はジェネリック・型レベルのプログ ラミングを強くサポート ○ shapeless の HList のように柔軟に扱える ■ 例) (Int, String) =:= Int *: String *: EmptyTuple ■ interface や構造は List を想像するとわかりやすい。 ○ 型レベル の Map や Fold, FlatMap まである https://github.com/lampepfl/dotty/blob/0.26.0-RC1/library/src/scala/Tuple.scala

Slide 18

Slide 18 text

Tuple (実践) ● 今回の Design 型 は bind された factory (Entry) をすべて Tuple として 型パラメータEntries に追 加していく ○ イメージ: Design[Entry2 *: Entry1 *: EmptyTuple] ● bind された様々な型を持ち運ぶことができる。(これ により依存の充足をコンパイル時に判断できる)

Slide 19

Slide 19 text

given / using

Slide 20

Slide 20 text

given / using ● Dotty 版の implicit parameter (誤解を恐れずに言えば ) ○ implicit な値, Function の宣言に given ○ 引数として implicit な値を利用する際に using ○ いくつか細かい機能に変更がある ■ import 時に通常の値と区別 ■ Not による優先順位の指定 ■ ... ● Scala 2 の implicit で使えた再帰的な検索のテク ニックも健在 ○ 今回多用している

Slide 21

Slide 21 text

given / using (再帰的な検索) ● using を 持った given ○ using なものが見つかった場合に given が成り立つ。 ○ Scala 2 の implicit def x(implicit y: Y): X = ??? というパ ターン

Slide 22

Slide 22 text

given / using (実践) ● 依存グラフの作成に再帰的な given のサーチを 利用

Slide 23

Slide 23 text

given / using (実践) ● 依存グラフの作成に再帰的な given のサーチを 利用 ● Resolve[Out <: Tuple] は Design に登録されている Entries から Out 型を 生成できる apply メソッドをもつ ○ Resolved[Out] ≒ Out だと思ってもらって良い ● このインスタンスが存在するということは、つまり、 Design は Out 型を生成 するのに十分なファクトリが登録されている ということ。

Slide 24

Slide 24 text

given / using (実践) ● 依存グラフの作成に再帰的な given のサーチを 利用 ● given … as Resolve[EmptyTuple] は using なしで宣言されている。 ○ つまり、いずれにしても EmptyTuple を生成することは可能 (それはそう)

Slide 25

Slide 25 text

given / using (実践) ● 依存グラフの作成に再帰的な given のサーチを 利用 ● given … as Resolve[Head *: Tail] は using が宣言された上で定義されている。 ○ つまり、以下 using の条件を満たす場合に Head *: Tail は生成できる 1. Head の型を返却する Entry が Entries の中に存在する (FindEntry[Entries, Head]) 2. 2. の Entry が依存する引数リストが生成できる (findEntry.In) ● この引数リストもまた Tuple なので、これを再帰的に検索 ● 引数なしのものは EmptyTuple を引数として登録しており、そこが再帰の終了条件とな る 3. Tail が生成できる (Resolve[Tail]) ● Dotty は標準ライブラリで Tuple が generic に扱えるようになったのでこのような再帰的なテクニックが活 用しやすい

Slide 26

Slide 26 text

given / using (実践) ● Design の resolveAll メソッドは Resolve[Outputs[Entries]] 型 が given される場 合に利用できる。 ○ つまり、すべての依存を満たす場合にコンパイ ルでき、そうでない場合にコンパイルエラー

Slide 27

Slide 27 text

その他 Dotty の機能

Slide 28

Slide 28 text

その他 Dotty の機能 (感想) ● Optional Braces ○ {} のかわりに yaml っぽい感じにインデントでブロックを記 述できる ○ 議論されているときはウッと思ったけど意外に良いかも。 (縦に短くなる) ● Match types ○ 型のパターンマッチ ○ 超強力だが今回はあまり使っていない ■ Entry から出力型をとるのに使っている ○ ゴリゴリに使った例はこちら (宣伝) ■ Scala のコンパイラに FizzBuzz を解いてもらう (Dotty もあるよ)

Slide 29

Slide 29 text

その他 Dotty の機能 (感想) ● Extension Methods ○ よくある拡張メソッド ○ 記述は簡潔だが、implicit class にできたことができなく なってしまっている

Slide 30

Slide 30 text

その他 Dotty の機能 (ハマったところ) ● dependent function type を 保持して扱おうとし たが上手くいかなかった ○ 目的の Out を持つ Entry を Head から順に検索する findEntry trait ○ 下記 findEntryInTail は type In = findEntry.In とできそう なきがしたが、この In が後々上手く解決できない ○ 結局 Aux パターンを使った

Slide 31

Slide 31 text

その他 Dotty の機能 (ハマったところ) ● 再帰的な given の検索がなんか上手くいかない ケースがある(致命的) ○ given のサーチパスがバグっているようなきがする ■ 再帰したときに companion object の given を読んでくれないケースがあ る ○ Design class 内に必要な given を置いたパターンだとな ぜか上手くいくのでいったんそうしている ■ DI の 呼び出し方に癖があるので納得いっていない ■ Issue あげるかも

Slide 32

Slide 32 text

まとめ

Slide 33

Slide 33 text

まとめ ● Dotty で軽量な DI ライブラリをかいてみた ● 標準ライブラリの Tuple がいい感じに扱えるので 手軽に型レベル・ジェネリックに凝ったことができ る ● Dotty 今後に期待

Slide 34

Slide 34 text

参考

Slide 35

Slide 35 text

参考 ● Dotty ○ https://dotty.epfl.ch/ ● Shapeless ○ https://github.com/milessabin/shapeless