Scala 2 の Partially-Applied Type イディオムを Scala 3でシンプルにする

Scala 2 で定番だった Partially-Applied Type イディオムを Scala 3 で簡素化する解説。

## 結論
こんな感じの Scala 2 コードが、、、
```scala
final class PurePartiallyApplied[F[_]](val dummy: Boolean = true ) extends AnyVal {
  def apply[A](value: A)(implicit F: Applicative[F]): OptionT[F, A] =
    OptionT(F.pure(Some(value)))
}
def pure[F[_]]: PurePartiallyApplied[F] = new PurePartiallyApplied[F]
```
Scala 3 でこんな風に書ける。
```scala
def pure[F[_]]: Applicative[F] ?=> [A] => A => OptionT[F, A] =
  [A] => (a: A) => OptionT(a.some.pure)
```


## 動機
関数の**部分適用**のように、型パラメータを複数持つ値の生成時にも、型アノテーションを必要最小限の部分指定に留めてできるだけ型推論させたいが、Scala 2 までは言語仕様上これが無理だった。

そのため [Partially-Applied Type](https://typelevel.org/cats/guidelines.html#partially-applied-type) イディオムといった工夫が編み出され、特に Cats などで多用されてきたが、使用側では無駄な型アノテーションは減ったものの、定義側ではボイラープレートが増えることになった。

幸い Scala 3 からは、メソッドだけではなく関数も polymorphic に定義できるようになるなど実装の選択肢が増えた。この記事では、Scala 3 の機能を利用して、Partially-Applied Type の定義側も簡潔に書く方法を示してみる。


## 今まで
[Cats の Guideline](https://typelevel.org/cats/guidelines.html#partially-applied-type) で説明に使われている `OptionT[F, A]`の生成コードをお題とする。

Partially-Applied Type イディオム以前の原始的なコードは以下のようなものだった。
```scala
def pure[F[_], A](a: A)(implicit F: Applicative[F]): OptionT[F, A] =
  OptionT(F.pure(Some(a)))

pure[List, Int](1) // OptionT[List, Int] = OptionT(List(Some(1)))
```
簡単に書けるし動くことは動くが、実引数 `1`で型が分かるのに更に `Int` を明示するのが微妙に無駄に見える。

Partially-Applied Type イディオムでは、型パラメータをクラス定義と `apply` に別けて置くことで、「型の部分適用」を実現する。

```scala
final class PurePartiallyApplied[F[_]](val dummy: Boolean = true) extends AnyVal {
  def apply[A](value: A)(implicit F: Applicative[F]): OptionT[F, A] =
    OptionT(F.pure(Some(value)))
}
def pure2[F[_]]: PurePartiallyApplied[F] = new PurePartiallyApplied[F]

pure2[List](1) // OptionT[List, Int] = OptionT(List(Some(1)))
```
とはいえ、もともとは実質一行だったメソッド定義に比べて大幅にボイラープレートが増えている。以下、これを Scala 3 の言語機能で簡素化する。

## 改良
段階的に変形する。まず準備として Scala 3 と Cats の syntax でノイズを減らしておく[^1]。
```scala
final class PurePartiallyApplied3[F[_]]:
  def apply[A](value: A)(using Applicative[F]): OptionT[F, A] =
    OptionT(value.some.pure)

def pure3[F[_]]: PurePartiallyApplied3[F] = PurePartiallyApplied3[F]
```

次に Scala 3 の [Context Functions](https://docs.scala-lang.org/scala3/reference/contextual/context-functions.html) で、`Applicative[F]`型の context parameter を取る関数を返すように変更する。
```scala
final class PurePartiallyApplied4[F[_]]:
  def apply[A](value: A): Applicative[F] ?=> OptionT[F, A] =
    OptionT(value.some.pure)

def pure4[F[_]]: PurePartiallyApplied4[F] = PurePartiallyApplied4[F]
```
さらに Scala 3 の [Polymorphic Function Types](https://docs.scala-lang.org/scala3/reference/new-types/polymorphic-function-types.html) で、型パラメータ[A] も値域側に移動する。ついでにメソッドから関数に変えておく。
```scala
final class PurePartiallyApplied5[F[_]]:
  val pure: Applicative[F] ?=> [A] => A => OptionT[F, A] =
    [A] => (a: A) => OptionT(a.some.pure)

def pure5[F[_]: Applicative]: [A] => A => OptionT[F, A] = PurePartiallyApplied5[F].pure
```

この時点ですでに `PurePartiallyApplied` クラスの必要性が無いので、1個のメソッドにまとめられる。
```scala
def pure[F[_]]: Applicative[F] ?=> [A] => A => OptionT[F, A] =
  [A] => (a: A) => OptionT(a.some.pure)
```
動作も想定通り:bulb:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9633/318a5735-5b82-1477-359b-7541eb6df36f.png)

## 参考
- [Kinda-Curried Type Parameters](https://tpolecat.github.io/2015/07/30/infer.html)
- [context function](https://docs.scala-lang.org/scala3/reference/contextual/context-functions.html)
- [polymorphic function types](https://docs.scala-lang.org/scala3/reference/new-types/polymorphic-function-types.html)

 [^1]: フットプリント等のパフォーマンス要素は一旦考慮から外して、ここで `AnyVal` と dummy パラメータを除いた。