1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

結論

こんな感じの Scala 2 コードが、、、

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 でこんな風に書ける。

def pure[F[_]]: Applicative[F] ?=> [A] => A => OptionT[F, A] =
  [A] => (a: A) => OptionT(a.some.pure)

動機

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

そのため Partially-Applied Type イディオムといった工夫が編み出され、特に Cats などで多用されてきたが、使用側では無駄な型アノテーションは減ったものの、定義側ではボイラープレートが増えることになった。

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

今まで

Cats の Guideline で説明に使われている OptionT[F, A]の生成コードをお題とする。

Partially-Applied Type イディオム以前の原始的なコードは以下のようなものだった。

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 に別けて置くことで、「型の部分適用」を実現する。

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

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 で、Applicative[F]型の context parameter を取る関数を返すように変更する。

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 で、型パラメータ[A] も値域側に移動する。ついでにメソッドから関数に変えておく。

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個のメソッドにまとめられる。

def pure[F[_]]: Applicative[F] ?=> [A] => A => OptionT[F, A] =
  [A] => (a: A) => OptionT(a.some.pure)

動作も想定通り:bulb:
image.png

参考

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?