2
1

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 5 years have passed since last update.

Distributive という地味な型クラス 〜 Traverse の双対

Last updated at Posted at 2018-02-24

Traverse の双対である Distributive という Cats の型クラスについて

はじめに

Cats の 型クラス階層で、Functor の下にDistributive というものがある。Traverse双対で、簡略化した定義は以下のようなものになる。

trait Distributive[F[_]] extends Functor[F] {
  def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => F[B]): F[G[B]]
  def cosequence[G[_]: Functor, A](ga: G[F[A]]): F[G[A]] = ...
  def compose[G[_]](implicit G0: Distributive[G]): Distributive[λ[α => F[G[α]]]] = ...
}

これについて少し調べて素振りコードを書いたので、紹介したい。

※ ソースはここに置いた。Cats は 2.0.0-M1 を使った。

distribute関数 と cosequence関数

Cats では、Function0Function1NestedKleisliTuple2Kについて、Distributive インスタンスが提供されている。

それぞれについて distributecosequence を使った簡単なサンプルコードを書いてみる。cats.syntax.distributive で提供されている distributive 構文も、適宜使用する。

Haskell の Distributive と関数名が違うが、最後の方に補足した。

Function0

Distributiveインスタンスが提供されているもののうち一番シンプルなもの。

// F = () => ?
val d0 = Distributive[() => ?]

// G = List, A = Char, B = String
// G[A] = List[Char], F[B] = () => String, F[G[B]] = () => List[String]
val afb0: Char => () => String = c => () => s"<$c>"
val ga0 = List('a', 'b')

d0.distribute(ga0)(afb0).apply // List(<a>, <b>)
ga0.distribute(afb0).apply     // List(<a>, <b>)

// G = List, A = Char
// G[F[A]] = List[() => Char], F[G[A]] = () => List[Char]
val gfa0 = List.fill(3)(() => '7')

d0.cosequence(gfa0).apply            // List(7, 7, 7)
gfa0.cosequence[() => ?, Char].apply // List(7, 7, 7)

Function1

パラメータを一つ受け取る関数の型Function1Distributiveになる。ここではFunction1[Int, ?]を使った。

// F = Int => ?
val d1 = Distributive[Int => ?]

// G = Option, A = Char, B = String
// G[A] = Option[Char], F[B] = Int => String, F[G[B]] = Int => Option[String]
val afb1: Char => Int => String = c => n => c.toString * n
val ga1 = Option('a')

d1.distribute(ga1)(afb1).apply(3) // Some(aaa)
ga1.distribute(afb1).apply(3)     // Some(aaa)

// G = Option, A = String
// G[F[A]] = List[() => Char], F[G[A]] = () => List[Char]
val gfa1: Option[Int => String] = Option(n => "a" * n)

d1.cosequence[Option, String](gfa1).apply(3) // Some(aaa)
d1.cosequence[Option, String](None).apply(3) // None
gfa1.cosequence[Int => ?, String].apply(4)   // Some(aaaa)

Nested

あまり馴染みのないデータタイプだけど、Option[List[T]]のような型を操作するときに、このNestedを使えば、ListT のようなモナドトランスフォーマーを敢えて作らなくても、大抵の場合こと足りるという(参考)。

簡略化した定義は以下のようなもの。

final case class Nested[F[_], G[_], A](value: F[G[A]]) {
  def mapK[H[_]](f: F ~> H): Nested[H, G, A] = Nested(f(value))
}

これも以下のようにDistributiveとして使うことができる。

// F = Nested[() => ?, Int => ?, A]
type FN[A] = Nested[() => ?, Int => ?, A]
val dn = Distributive[FN]

// G = Option, A = Char, B = String
// G[A] = Option[Char], F[B] = Int => String, F[G[B]] = Int => Option[String]
val afbn: Char => FN[String] = c => Nested(() => c.toString * _)
val gan = Option('a')

dn.distribute(gan)(afbn).value()(5) // Some(aaaaa)
gan.distribute(afbn).value()(3)     // Some(aaa)

dn.distribute(gan)(afbn)
  .map(_.map(s => s"<$s>")) // <-- (a)
  .value()(3)               // Some(<aaa>)

// G = List, A = String
// G[F[A]] = List[FN[String]], F[G[A]] = FN[List[String]]
val gfan: List[FN[String]] = List(Nested(() => "n" * _), Nested(() => "m" * _))
val nested = dn.cosequence[List, String](gfan)

nested.value()(3)                      // List(nnn, mmm)
nested.map(os => s"<$os>").value()(2)  // <List(nn, mm)>
gfan.cosequence[FN, String].value()(2) // List(nn, mm)

val func0ToId: Function0 ~> Id = new (Function0 ~> Id) {
  override def apply[A](fa: () => A) = fa()
}
gan.distribute(afbn).mapK(func0ToId).value(3) // Some(aaa)

Nestedの動きをみるコードも若干書いてみた。(a)行のとおり、一個のmap関数で、Nested内の2層の Functor の中の値を操作できる。また最後の行では、mapKFunction0からIdへの自然変換を与えて、value.apply().apply(n)の中間のapply()を外している。

Kleisli

おなじみのKleisliDistributiveインスタンスが提供されているが、ただしKleisli[F[_], A, B]FDistributiveである必要がある。ここではFunction0を用いた。

// F = Kleisli[() => ?, Int, A]
type FK[A] = Kleisli[() => ?, Int, A]
val dk = Distributive[FK]

// G = Option, A = Char, B = String
// G[A] = Option[Char], F[B] = Int => String, F[G[B]] = Int => Option[String]
val afbk: Char => FK[String] = c => Kleisli(n => () => c.toString * n)
val gak = Option('a')
dk.distribute(gak)(afbk).run(7)() // Some(aaaaaaa)
gak.distribute(afbk).run(7)()     // Some(aaaaaaa)

// G = Option, A = String
// G[F[A]] = Option[FK[String]], F[G[A]] = FK[Option[String]]
val gfak: Option[FK[String]] = Option(Kleisli(n => () => "k" * n))
dk.cosequence[Option, String](gfak).run(8)() // Some(kkkkkkkk)
dk.cosequence[Option, String](None).run(8)() // None

gfak.cosequence[FK, String].run(6)() // Some(kkkkkk)

Tuple2K

Tuple2K については別記事に書いたが、簡略化した定義は以下のようなもので、これも Distributive のインスタンスが提供されている。ただしFGDistributive である必要がある。

final case class Tuple2K[F[_], G[_], A](first: F[A], second: G[A]) {
  def mapK[H[_]](f: G ~> H): Tuple2K[F, H, A] = Tuple2K(first, f(second))
}

product になる一対のDistributiveファンクタは、Int => ?Boolean => ?を使って試してみた。

// F = Tuple2K[Int => ?, Boolean => ?, A]
type FT[A] = Tuple2K[Int => ?, Boolean => ?, A]
val dt = Distributive[FT]

// G = Option, A = Char, B = String
// G[A] = Option[Char], F[B] = Int => String, F[G[B]] = Int => Option[String]
val afbt: Char => FT[String] = c => Tuple2K(c.toString * _, b => s"<$c, $b>")
val gat = Option('a')

dt.distribute(gat)(afbt).first(2) // Some(aa)
gat.distribute(afbt).second(true) // Some(<a, true>)

// G = Option, A = String
// G[F[A]] = Option[FK[String]], F[G[A]] = FK[Option[String]]
val gfat: Option[FT[String]] = Option(Tuple2K(n => s"<n>", b => s"<$b>"))

dt.cosequence[Option, String](None).first(2) // None
gfat.cosequence[FT, String].second(false)    // Some(<false>)

compose関数

Distributiveにはcomposeが提供されていて、以下のように合成できる。

type TC[A] = Int => Double => A
val composed: Distributive[TC] = Distributive[Int => ?].compose[Double => ?]
def f(o: Option[TC[String]]) = composed.cosequence[Option, String](o).apply(3)(0.14)

f(Option(n => d => s"π ≒ ${n + d}")) // Some(π ≒ 3.14)
f(None)                              // None

Distributive[Int => ?]Distributive[Double => ?]から合成したDistributive[Int => Double => ?]cosequence
Option[Int => Double => String]を与えると、型Int => Double => Option[String]の結果が得られる。

Traverse の双対

冒頭で書いた通り、DistributiveTraverseの双対で、distributetraverseに、cosequencesequenceにそれぞれ対応する。

コードで表現すると以下のようになる。

val h: Double => Int => String = d => n => s"${n+d}"
val o = Option(0.14)

val s1 = Distributive[Int => ?].distribute[Option,   Double, String](o)(h)
val t1 = Traverse    [Option]  .traverse  [Int => ?, Double, String](o)(h)

assert(s1(3) == t1(3)) // 共に Some(3.14)

val of: Option[Int => String] = Option(n => s"n = $n")

val s2 = Distributive[Int => ?].cosequence[Option,   String](of)
val t2 = Traverse    [Option]  .sequence  [Int => ?, String](of)

assert(s2(100) == t2(100)) // 共に Some(n = 100)

補足: Haskell と Scalaz との対応表

FDistributiveGFunctor とすると、Haskell、Cats、Scalaz で以下のように対応する。

入力型 結果型 Haskell Cats Scalaz メモ
G[A], A→F[B] F[G[B]] collect distributive distributive
G[F[A]] F[G[A]] distributive cosequence cosequence
G[A]→B, G[F[A]] F[B] cotraverse --- cotraverse traverse の矢印を逆にしたもの
2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?