Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dotty で軽量な DI ライブラリをかいてみた

Dotty で軽量な DI ライブラリをかいてみた

75086cfa4a46d4aa47deb38b409bd9cd?s=128

Jun Tomioka

August 26, 2020
Tweet

Transcript

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

  2. Jun Tomioka @jooohn1234 Software Engineer at M3, Inc.

  3. None
  4. Dotty で軽量な DI ライブラリを書いてみた • つくったもの紹介 • テクニック ◦ Tuple

    ◦ given • その他 Dotty の機能 • まとめと感想 ※今回試したバージョンは 0.26.0-RC1
  5. 謝罪 • 細かい解説は完全に Rookies 向けじゃない内容 になってしまいました・・・。全てを理解する必要は ありません! • 工夫すればコンパイル時にいろいろなことができ るんだな、と興味を持つきっかけになれば幸いで

    す!
  6. Dotty とは • ざっくりいうと Scala 3 のコンパイラ ◦ https://dotty.epfl.ch/docs/index.html

  7. つくったもの 紹介

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

  9. None
  10. 以下のような依存関係 Baz Bar Foo Int

  11. • Design.of[Try] で Try を文脈として扱うことを宣言。このコンテナ作成は 失敗する可能性がある (Success | Failure) ◦

    利用者がこの文脈を選べる (Future, Resource 等)。 ◦ Tagless Final スタイル • bind は コンテナに登録するインスタンスのファクトリを指定。ここでは単 に Foo, Bar のapply を指定。 ◦ それぞれ Int => Foo, Foo => Bar が登録されている。 • bindF は Try[Baz] を返すファクトリを登録。渡された Int が負数の場合 に失敗。
  12. • この時点で依存を解決しようとしてもコンパイルエラーとなる。 • Foo, Bar, Baz のファクトリは登録されているが、 Baz と Foo

    が依存して いる Int が登録されていないため。 Baz Bar Foo Int
  13. • give ではファクトリではなく値を直接登録。 • Baz の生成は、Int の値によって挙動が変わっている ◦ 1 を渡した方はいい感じに

    Success している ◦ -1 を渡した方は Failure している
  14. 特徴 • 機能 ◦ コンパイル時に依存の充足をチェック ▪ 必要な依存が未登録の場合にコンパイルエラー ◦ 任意の文脈 (Try,

    Future 等) で実行可能 ▪ 非同期処理を挟んだり、リソース管理したりできる • コード ◦ core 部分 100行未満のDIコンテナ ▪ (一般的な型クラス・ADTの自前実装を除く) ◦ 依存は標準ライブラリのみ ◦ マクロ未使用 ▪ compiletime package も未使用
  15. テクニック

  16. Tuple

  17. 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
  18. Tuple (実践) • 今回の Design 型 は bind された factory

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

  20. given / using • Dotty 版の implicit parameter (誤解を恐れずに言えば )

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

    using なものが見つかった場合に given が成り立つ。 ◦ Scala 2 の implicit def x(implicit y: Y): X = ??? というパ ターン
  22. given / using (実践) • 依存グラフの作成に再帰的な given のサーチを 利用

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

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

    given … as Resolve[EmptyTuple] は using なしで宣言されている。 ◦ つまり、いずれにしても EmptyTuple を生成することは可能 (それはそう)
  25. 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 に扱えるようになったのでこのような再帰的なテクニックが活 用しやすい
  26. given / using (実践) • Design の resolveAll メソッドは Resolve[Outputs[Entries]]

    型 が given される場 合に利用できる。 ◦ つまり、すべての依存を満たす場合にコンパイ ルでき、そうでない場合にコンパイルエラー
  27. その他 Dotty の機能

  28. その他 Dotty の機能 (感想) • Optional Braces ◦ {} のかわりに

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

    記述は簡潔だが、implicit class にできたことができなく なってしまっている
  30. その他 Dotty の機能 (ハマったところ) • dependent function type を 保持して扱おうとし

    たが上手くいかなかった ◦ 目的の Out を持つ Entry を Head から順に検索する findEntry trait ◦ 下記 findEntryInTail は type In = findEntry.In とできそう なきがしたが、この In が後々上手く解決できない ◦ 結局 Aux パターンを使った
  31. その他 Dotty の機能 (ハマったところ) • 再帰的な given の検索がなんか上手くいかない ケースがある(致命的) ◦

    given のサーチパスがバグっているようなきがする ▪ 再帰したときに companion object の given を読んでくれないケースがあ る ◦ Design class 内に必要な given を置いたパターンだとな ぜか上手くいくのでいったんそうしている ▪ DI の 呼び出し方に癖があるので納得いっていない ▪ Issue あげるかも
  32. まとめ

  33. まとめ • Dotty で軽量な DI ライブラリをかいてみた • 標準ライブラリの Tuple がいい感じに扱えるので

    手軽に型レベル・ジェネリックに凝ったことができ る • Dotty 今後に期待
  34. 参考

  35. 参考 • Dotty ◦ https://dotty.epfl.ch/ • Shapeless ◦ https://github.com/milessabin/shapeless