Hammock による Free Monad な HTTPクライント

Hammock という純粋関数型HTTPクライアントライブラリがある。HTTP アクセスを Free Monad で表現していて、*effect* のパラメータ化技法の応用例として面白いので、今回はこれを試してみる。

## 概要
- [Hammock](https://github.com/pepegar/hammock) は、型安全かつ純粋な関数型 HTTP クライアントライブラリ
- *effect* をパラメータ化する技法として**Free Monad** が活用されている。
  - **HttpF**: HTTP の8つの動詞を Free Monad のいわゆる *Algebra* で 表現
  - **MarshallF**: レスポンス中の Entity から他の型 `A` へのアンマーシャルも Free Monad で表現
- *effect* は `Sync[F]` を持つ任意の `F[_]` が使える（典型的には `IO` や `Task` など）。
- 既存の HTTP クライアントをベースで利用していて、それぞれの Free Monad インタープリターが付属モジュールとして提供される。JVM 用だと、[Apach HttpComponent Client](http://hc.apache.org/httpcomponents-client-ga/index.html), [HttpAsyncClient](http://hc.apache.org/httpcomponents-asyncclient-dev/index.html), [ Akka HTTP Client API](https://doc.akka.io/docs/akka-http/current/client-side/index.html) から選択可能。

## サンプル実装
[公式サイトのサンプル](http://pepegar.com/hammock/) をベースにして、[Cats Effect](https://typelevel.org/cats-effect/)、[Monix](https://monix.io/)、[Iota](https://github.com/frees-io/iota) などの要素を取り入れて実装してみる。

#### 使ったバージョンなど
- hammock: "0.9.0"
- circe: "0.10.0"
- monix: "3.0.0-RC2"
- iota: "0.3.10"

## 一番簡単なサンプル
一般に Free Monad を使った Scala プログラムは、下のような部分からなる。

1. **代数**: いわゆる *Algebra* の定義
2. **リフト/スマートコンストラクタ**: *Algebra* から Free Monad へのリフトとスマートコンストラクタ
3. **インタプリタ**: Free Monad から他のモナドへの変換
4. **記述**: *Algebra*を使ったプログラムの記述
5. **実行**: プログラムにインタプリタを与えて具体的に実行する部分

このうち、Hammock では、(1) の代数（HttpF, MarshallF）、(2) リフト/スマートコンストラクタ、(3)ベース HTTPクライアントごとの各インタプリタが、モジュールとして提供される。

(4)のプログラムの記述は、Hammock のユーティリティメソッドを使って、動詞、URL、ヘッダーなどから `Free[HttpF, HttpResponse]` を得た上で、必要に応じてレスポンスをデコード→アンマーシャルしたり、`map`、`flatMap` して変換したりといった操作を追加して組み立てる。

もっとも簡単な例は、以下のようなものになる。

```scala
val program: Free[HttpF, HttpResponse] = Hammock
  .request(Method.GET, uri"http://httpbin.org/get", Map())
```

この `program` にインタープリタを与えて実行する部分(5)は、以下のようになる。ベースの HTTPクライアントには Apache Http Client、*effect* としては Cats Effect の `IO` を使ってみた。

```scala
object SimpleMain extends IOApp {
  ...
  implicit val interpreter: InterpTrans[IO] = ApacheInterpreter[IO]

  def run(args: List[String]): IO[ExitCode] = for {
    res <- program.exec[IO]        // ここで Free から IO に変換
    _   <- IO(println(res.entity))
  } yield ExitCode.Success
}
```
`run` メソッド内の `program.exec[IO]` の行で、`Free[HttpF, HttpResponse]` が `IO[HttpResponse]` に変換される。このときに implicit な `ApacheInterpreter` インタープリタが参照される。

実行すると [httpbin](http://httpbin.org/get) からリクエストの内容をそのまま表すレスポンスが返ってくる。 

[ソース](https://gist.github.com/yasuabe/042bc28df9cb41cff165278492bc074d)

## 複数の Free Monad の合成
次に、公式の [Algebras](http://pepegar.com/hammock/algebras.html) のサンプルをベースにしつつ、[Iota](https://github.com/frees-io/iota) を使って複数 Free Monad の合成をシンプルに書いてみる（[Iota の過去記事](https://qiita.com/yasuabe2613/items/4202dd51469e5e56ddd1)）。

まずコンソール入出力のための自前の Free Monad 代数 `ConsoleF` を、次のように定める。

```scala
object Console {
  sealed trait ConsoleF[A]
  case object Read               extends ConsoleF[String]
  case class  Write(msg: String) extends ConsoleF[Unit]
  ...
  def trans[F[_]](implicit F: Sync[F]): ConsoleF ~> F = new (ConsoleF ~> F) {
    def apply[A](ca: ConsoleF[A]): F[A] = ca match {
      case Read       => F.delay(scala.io.StdIn.readLine())
      case Write(msg) => F.delay(println(msg))
    }
  }
}
```
この `ConsoleF` と Hammock の `HttpF`、`MarshallF` を全て合成した型 `App` と、`App` から `F[_]` への変換 `trans` を次のように定めておく。

```scala
type App[A] = CopK[MarshallF ::: HttpF ::: ConsoleF ::: TNilK, A]

implicit def trans[F[_]](implicit S: Sync[F]): App ~> F = CopK.FunctionK.of(
  marshallNT[F], ApacheInterpreter[F].trans, Console.trans(S))
```
`HttpF`、`MarshallF` のスマートコンストラクタは Iota の `CopK.Inject` をサポートしていないため、下記のように暗黙の `InjectK` [^inject] を用意しておく。

```scala
  implicit def httpI:     InjectK[HttpF,     App] = CopK.Inject[HttpF,     App]
  implicit def marshallI: InjectK[MarshallF, App] = CopK.Inject[MarshallF, App]
```
[^inject]: ちなみにここで言う `Inject[A, B]`、`InjectK[A, B]`とは、「Open Union 型 `B[_]` に `A[_]` が含まれる」といった事実を示すエビデンス的な意味で DI 的な意味での Inject とは関係ない。

これらを使うと、以下のようにプログラムが記述できる。

```scala
def program(implicit
  Console:  ConsoleC[App],
  Marshall: MarshallC[App],
  Hammock:  HttpRequestC[App]
): Free[App, String] = for {
  _        <- Console.write("What's the ID?")
  id       <- Console.read
  response <- Hammock.get(uri"https://jsonplaceholder.typicode.com/users?id=${id.toString}", Map())
  parsed   <- Marshall.unmarshall[String](response.entity)
} yield parsed
```

実行するコードは先述の `SimpleApp` と同様だが、ここは monix の `Task` を使ってみた。

```scala
override def run(args: List[String]): Task[ExitCode] = for {
  entity <- program foldMap trans[Task]
  _      <- Task { println(entity) }
} yield ExitCode.Success
```
コンソールで "What's the ID?" と聞かれるので、"4"などを入力するとダミーのユーザ情報[^JSONPlaceholder]が返ってくる。

[ソース](https://gist.github.com/yasuabe/9b2656c16aebdb092b2eb8abf4807b8c)
[^JSONPlaceholder]: [JSONPlaceholder](https://jsonplaceholder.typicode.com/) というテスト用のフェイク REST API サイトを使用。

## 補足と所感
- ここでは触れなかったが、[circe](https://circe.github.io/circe/) や [monocle](http://julien-truffaut.github.io/Monocle/) を使ったリクエスト／レスポンスの操作・DSLも提供されている。

- 上の Free Monad のサンプルでは、やや多めに Free Monad を使うコードを書いてみたが、`Hammock.request(...)` 等で `Free[F, HttpResponse]` を得た直後に `.exec[IO]`や`.exec[Task]`をつなげて `IO` や `Task` に変換すると、逆に Free Monad の使用をあえて見せないようなコーディングもできる。

- Scala 界隈での、Free Monad の全盛期といえば、多分『[Functional and Reactive Domain Modeling](https://www.manning.com/books/functional-and-reactive-domain-modeling)』が出版された 2016 年ごろかなと個人的には思っていて、今思えばその頃はファッション的に濫用されすぎていた気がしないでもない。その後 Tagless Final の台頭とともに、Free Monad が徐々にオワコン化してきた一方で、むしろ濫用が収まって代数化のメリットがデメリット（特にボイラープレートなど）を普通に上回るような、正しい使い方が残ってきたのかなと思う[^nice_free_monads]。この Hammock も、そうした良い Free Monad の活用という感触はあった。

[^nice_free_monads]: [この辺りの記事](https://blog.softwaremill.com/final-tagless-seen-alive-79a8d884691d)では、そうした良い Free Monad の例として Slick の DBIOAction や doobie の ConnectionIO などが挙げられている。