DI とモナドの差し替え：Tagless Final / Free Monad

以前書いた記事『[Scalaでいろんな DIを試してみる Cake 〜 Free+ReaderT](https://qiita.com/yasuabe2613/items/d1e0451024f61145f875)』で、モナドの決定までを含めて依存と考えて、Cake パターンや Free Monad などDIの方式を比べてみたが、Tagless Final の試用などいくつか課題が残った。これらについて、前回と同じ単純な題材で試してみる。

#### 使用バージョンなど
- Scala: 3.1.3
- cats: 2.8.0
- cats effect: 3.3.12

## 方針
以下の記述で、特定のモナドに依存しないコードが属する層をドメイン層[^1]、具体的なモナドについてドメインモデルを実行／評価する層をアプリケーション層と呼ぶことにする。

この２層を念頭に置きながら、「ドメイン層で`MovieService`は`MovieRepo`に依存する。具体的な`MovieRepo`実装とモナドはアプリケーション層で初めて関連づけられる。」という共通のお題を考える。

これを以下の順でやってみる。

- Tagless Final 単体
- Minimal Cake + Tagless Final
- Minimal Cake + Free Monad
- Tagless Final + Free Monad
- Minimal Cake + Tagless Final + Free Monad


## Tagless Final 単体

まず前回課題として残った Tagless Final から試してみる。簡単に言うと、パラメータ化した型コンストラクタでモナドの部分を置き換えて、特定モナド用のインタプリタは後で与えるというものになる。

[前回の Free Monad 版](https://qiita.com/yasuabe2613/items/d1e0451024f61145f875#free-monad)ではひと組の ADT を、sealed trait と case class で定義したが、Tagless Final では、trait で束ねたメソッドの組で表現する。[Oleg Kiselyov の講義資料](http://okmij.org/ftp/tagless-final/course/lecture.pdf)では、これを *symantics* と呼んでいるが、巷のブログなどでは'[algebra](https://softwaremill.com/free-tagless-compared-how-not-to-commit-to-monad-too-early/)'や'[language](https://blog.scalac.io/exploring-tagless-final.html)'と呼んでいるものもある。

```scala
trait MovieRepoSym[F[_]]:
  def getMovie(id: Int): F[Option[Movie]]
```

使う側では渡された`sym: MovieRepoSym`のメソッドをシンプルにただ評価する。

```scala
class MovieService[F[_]: Monad](sym: MovieRepoSym[F]):
  def getMovie(id: Int): F[Option[Movie]] = sym.getMovie(id)
```
ただし`sym`の渡し方については、上のコードのようにクラスのコンストラクタで渡す方式（[参考](https://softwaremill.com/free-tagless-compared-how-not-to-commit-to-monad-too-early/)）や、implicit パラメータでメソッドに渡す方式([参考](https://blog.scalac.io/exploring-tagless-final.html))など、バリエーションが色々あるらしい。


アプリケーション層では、具体的なモナドを決めたインタプリタを定義する。Free Monad では自然変換（`FunctionK` あるいは `~>`）だったが、Tagless Finalでは`MovieRepoSym`の実装クラスになる。

```scala
object IOInterpreter extends MovieRepoSym[IO]:
  def getMovie(id: Int): IO[Option[Movie]] = IO(db.get(id))

val service = MovieService(IOInterpreter)
val program = service.getMovie(42)
```
ここでは cats effect の `IO` をモナドとしてみた。この`IO`インタプリタを与えて`MovieService`インスタンスを生成し、プログラムを記述する（ここでは `getMovie`だけだが）。

得られた `IO`を「宇宙の終り[^the_end_of_the_universe]」に実行して以下のように結果を得る。

```scala
program.unsafeRunSync()
//Some(Movie(42,A Movie))
```
`IO` を使ったので遅延評価になっているが、これがもし `Future` だったとすると`getMovie` の評価と同時にその中身も実行されることになる。一方、Free Monad では ADTを組み合わせてプログラムを構築するだけなので、たとえ `Future`でも最後にインタプリタを適用するまで中のコードは実行されないので、この点は異なる。

また以下のように、モナドを換えて別途作成したインタプリタを関連づけて評価することもできる。

```scala
object TestInterpreter extends MovieRepoSym[Id]:
  def getMovie(id: Int): Option[Movie] = Option(Movie(-1, "Dummy"))

val id1 = MovieService(TestInterpreter).getMovie(42)
//Some(Movie(-1,Dummy))
```

##### Tagless Final 単体 まとめ

- シンプルで Scala のクラス定義の文法に自然に馴染む。
- symantics が増えたときに、どうやってインタプリタを渡すか悩むかもしれない。複数のパラメータでわたすか、合成したものを定義するか、などなど。
- [ソース: DI_tagless.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di2/DI_tagless.worksheet.sc)

## Minimal Cake + Tagless Final
[前回の Minimal Cake のコード](https://qiita.com/yasuabe2613/items/d1e0451024f61145f875#minimal-cake-pattern)で`IO`決め打ちにしていた部分を型パラメータにすると、自然と Tagless Final になる。

インジェクトする`MovieRepo`は、上の Tagless Final 単体の例で書いた`MovieRepoSym`と同じもので、つまりこれが Tagless Final の*symantics*に相当する。

```scala
trait MovieRepo[F[_]]:
  def getMovie(id: Int): F[Option[Movie]]

trait UsesMovieRepo[F[_]]:
  val movieRepo: MovieRepo[F]
```

インジェクト先の`MovieService`でもモナドがパラメータ化される（ここまでがドメイン層）。

```scala
trait MovieService[F[_]] extends UsesMovieRepo[F]:
  def getMovie(id: Int): F[Option[Movie]] = movieRepo.getMovie(id)}
```
アプリケーション層では、具体的なモナドについて`*Impl`と`MixIn*`を書く。特に`*Impl`は Tagless Final のインタプリタに相当し、上の Tagless Final 単体の例でいえば`IOInterpreter`と同じコードになる。

```scala
trait MixInMovieRepo:
  val movieRepo: MovieRepo[IO] = MovieRepoImpl

object MovieRepoImpl extends MovieRepo[IO]:
  def getMovie(id: Int): IO[Option[Movie]] = IO(db.get(id))
```
先の例では、`MovieService`のコンストラクタでインタプリタを渡していたが、Minimal Cake では`with MixInMovieRepo`でミックスインとしてインジェクトされる。実行も上と同様。

```scala
object MovieService extends MovieService[IO] with MixInMovieRepo
val program: IO[Option[Movie]] = MovieService.getMovie(42)
```

モナドの差し替えも自由にできる。以下では`Writer`を使って、呼び出しを記録してダミーを返すようにしてみた。

```scala
type LogWriter[V] = Writer[Chain[String], V]

val logWriterMovieRepo: MovieRepo[LogWriter] = new:
  def getMovie(id: Int): LogWriter[Option[Movie]] =
    Chain(s"getMovie($id)").tell as Movie(id, "Dummy").some

object TestMovieService extends MovieService[LogWriter]:
  val movieRepo = logWriterMovieRepo

TestMovieService.getMovie(42).run
//(Chain(getMovie(42)),Some(Movie(42,Dummy)))
```
##### Minimal Cake + Tagless Final まとめ
- Tagless Final 単体の場合にメソッドやコンストラクタに渡していた*symantics*のインタプリタは、継承関係の仕組みを使って mixin する。
- `MovieRepo`が*symantics*、`MovieRepoImpl`がインタプリタにそれぞれそのまま相当する。すでに書いた通り、Minimal Cake のモナドを型パラメータにすると、自然と Tagless final になる。 
- [ソース:DI_tagless_min_cake.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di2/DI_tagless_min_cake.worksheet.sc)


## Minimal Cake + Free Monad
ADTは、前回に見た[Free Monad 単体版](https://qiita.com/yasuabe2613/items/d1e0451024f61145f875#free-monad)と同じ。

```scala
enum Query[A]:
  case GetMovie(id: Int) extends Query[Option[Movie]]

type QueryF[T] = Free[Query, T]
```

[Minimal Cake 単体版](https://qiita.com/yasuabe2613/items/d1e0451024f61145f875#minimal-cake-pattern)で`Future`決め打ちだった、`MovieRepo`、`MovieService`の各部位を`QueryF`に置き換える。`UsesMovieRepo`はそのまま。

```scala
trait MovieRepo:
  def getMovie(id: Int): QueryF[Option[Movie]]

trait UsesMovieRepo:
  val movieRepo: MovieRepo

trait MovieService extends UsesMovieRepo:
  def getMovie(id: Int): QueryF[Option[Movie]] = movieRepo.getMovie(id)
```
Free Monad を使う通常のコーディングでは、ADT を`Free`に`liftF`する特有のボイラープレートを書くことになるが、Minimal Cake と併用する場合`*Impl`に書くと自然になじむ。

```scala
object MovieRepoImpl extends MovieRepo:
  def getMovie(id: Int): QueryF[Option[Movie]] = liftF(GetMovie(id))
```
具体的な依存をワイアリングするコードもドメイン層に書ける[^2]。

```scala
trait MixInMovieRepo:
  val movieRepo: MovieRepo = MovieRepoImpl

object MovieService extends MovieService with MixInMovieRepo
```

インタプリタは[Free Monad 単体版](https://qiita.com/yasuabe2613/items/d1e0451024f61145f875#free-monad)と同じ。実行も同様。

```scala
def ioInterpreter: Query ~> IO = new:
  def apply[A](fa: Query[A]): IO[A] = fa match
    case GetMovie(id) => IO(db.get(id))

val program = MovieService.getMovie(42).foldMap(ioInterpreter)
```

モナドを差し替えたテストも同じなので割愛する。

##### Minimal Cake + Free Monad まとめ
- Free Monad を導入する場合、ボイラープレートとなる`liftF`のブリッジをどこに置くか悩む予感がしていたが、自然と`*Impl`に置き場所が定まる。
- [ソース:DI_free_monad_min_cake.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di2/DI_free_monad_min_cake.worksheet.sc)

## Tagless Final + Free Monad
Tagless Final のパラメータ化されたモナドに Free Monad を当てはめると、二段階（あるいは多段階）でモナドを差し替えられるような構成になる。

以下の部分は Tagless Final と Free Monad のそれぞれの単体版と同じ。

```scala
enum Query[A]:
  case GetMovie(id: Int) extends Query[Option[Movie]]

type QueryF[T] = Free[Query, T]

trait MovieRepoAlg[F[_]]:
  def getMovie(id: Int): F[Option[Movie]]

class MovieService[F[_]: Monad](alg: MovieRepoAlg[F]):
  def getMovie(id: Int): F[Option[Movie]] = alg.getMovie(id)
```
インタプリタは Tagless Final 分と Free Monad 分の２通り書くことになるが、前者については以下のようにモナドを`QueryF`に半固定したデフォルト実装が書ける。内容は、上の Mininal Cake + Free Monad版の`MovieRepoImpl`とほぼ同じ。

```scala
object QueryFInterpreter extends MovieRepoAlg[QueryF]:
  def getMovie(id: Int): QueryF[Option[Movie]] = liftF(GetMovie(id))

val MovieServiceImpl = MovieService(QueryFInterpreter)
```

このデフォルト実装を使う場合、Free Monad 分のインタプリタとして、変換先を任意のモナド--例えば `IO` に固定した自然変換先をアプリケーション層で定義し、これを`foldMap`に渡して結果を得る。

```scala
def ioInterpreter: Query ~> IO = new:
  def apply[A](fa: Query[A]): IO[A] = fa match
    case GetMovie(id) => IO(db.get(id))

val program = MovieServiceImpl.getMovie(42).foldMap(ioInterpreter)
```

さらに、tagless final のパラメータとして`QueryF`とは別のモナドを直接与えることもできる。

```scala
val testMovieRepoImpl: MovieRepoAlg[Id] = new:
  def getMovie(id: Int): Option[Movie] = Movie(id, s"Movie($id)").some

val testMovieServiceImpl = MovieService(testMovieRepoImpl)

testMovieServiceImpl.getMovie(42)
// Some(Movie(42,Movie(42)))
```

##### Tagless Final + Free Monad まとめ
- Tagless Final の方が、*symantics* (algebra) の合成がやりやすいという説があるが、このシンプルすぎる例では特に利点はわからない。
- Tagless Final 単体の時と同様に、複数の*symantics*に依存する場合に渡し方で悩むかもしれない。
- [ソース:DI_tagless_final_free_monad.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di2/DI_tagless_final_free_monad.worksheet.sc)

## Minimal Cake + Tagless Final + Free Monad
一応やってみる・・・

「Minimal Cake + Free Monad」の Minimal Cake 部分をパラメータ化するか、あるいは「Tagless Final + Free Monad」の Tagless Final 部分を Minimal Cake 形式にするかすれば、「Minimal Cake + Tagless Final + Free Monad」になる。解説省略。
##### ドメイン層

```scala
case class Movie(id: Int, title: String)

enum Query[A]:
  case GetMovie(id: Int) extends Query[Option[Movie]]

type QueryF[T] = Free[Query, T]

trait MovieRepo[F[_]]:
  def getMovie(id: Int): F[Option[Movie]]

trait UsesMovieRepo[F[_]]:
  val movieRepo: MovieRepo[F]

trait MovieService[F[_]] extends UsesMovieRepo[F]:
  def getMovie(id: Int): F[Option[Movie]] = movieRepo.getMovie(id)

object MovieRepoImpl extends MovieRepo[QueryF]:
  def getMovie(id: Int): QueryF[Option[Movie]] = liftF(GetMovie(id))

trait MixInMovieRepo:
  val movieRepo: MovieRepo[QueryF] = MovieRepoImpl

object MovieServiceImpl extends MovieService[QueryF] with MixInMovieRepo
```

##### アプリケーション層

```scala
def ioInterpreter: Query ~> IO = new:
  def apply[A](fa: Query[A]): IO[A] = fa match
    case GetMovie(id) => IO(db.get(id))

val program = MovieServiceImpl.getMovie(42).foldMap(ioInterpreter)
```

- [ソース:DI_minimal_cake_tagless_final_free_monad.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di2/DI_minimal_cake_tagless_final_free_monad.worksheet.sc)

## 所感
- 記事を書き始めた当初、なんとなく Tagless Final + Free Monad を本命と考えていたが、やってみると Minimal Cake + Tagless Final か、 Minimal Cake + Free Monadが良さげに見える。
- 個人的には、ドメインモデル全体のブラックボックステストへの関心などから'program as data'という考え方に可能性を感じるので、Free Monad はオワコン的な空気を微妙に感じつつも、Minimal Cake + Free Monad に惹かれるものがある。
- Tagless Final と Free Monad を併用した上の二組は、いまのところ特に用途が思いつかない。ただし今回試さなかったが、ある程度、規模と複雑さが増したリアルなドメインを考えると複数の *algebra* ／*ADT*の合成が肝になるはずで、その辺りから効いてくるのかもしれない。
- ~~`Future` を使っていた部分を `monix.eval.Task` に変更。(2019/02/18)~~
- Scala を 3 に、その他のライブラリも最新に変更。(2022/08/10)
- `Task` を使っていた部分を `IO` に変更。(2022/08/10) [^task-to-io]

## 参考
- [Free and tagless compared - how not to commit to a monad too early](https://softwaremill.com/free-tagless-compared-how-not-to-commit-to-monad-too-early/)
- [Free Monad vs Tagless Final](https://medium.com/@agaro1121/free-monad-vs-tagless-final-623f92313eac)
- [Exploring Tagless Final pattern for extensive and readable Scala code](https://blog.scalac.io/exploring-tagless-final.html)
- [Typed Tagless Final Interpreters](http://okmij.org/ftp/tagless-final/course/lecture.pdf)

--------------------
[^1]: モナドを差し替え可能にすることのそもそもの動機は、単に実運用とテストでモナドを換えたいということよりも、むしろドメインモデルから「どのように実行されるか(How)」を減らして、「なにを実行するのか(What)」を記述するビジネスロジックとしての純度を高めたいという意図もある。
[^2]: `*Impl`、`MixIn*、`*Service`(object の方)について、特定のモナドに依存していないとはいえ、純粋にビジネスロジックかというと若干微妙なので、ドメイン層に置くべきかどうかは一考の余地あり。
[^the_end_of_the_universe]:純粋関数型界隈ではサイド・エフェクトを伴う関数によって組み立てられたプログラムを実行する最後の瞬間を "the end of the world/universe" と言う。
[^task-to-io]: この時点での monix が Cats Effect 3に対応していなかったため