アプリカティブを使用すると、コンテキスト内の関数をコンテキスト内の値に適用できます。したがって、たとえば、に適用some((i: Int) => i + 1)
しsome(3)
て取得できますsome(4)
。それは今は忘れましょう。それについては後で説明します。
リストには 2 つの表現があり、どちらかNil
またはhead :: tail
です。を使用して折り畳むのに慣れているかもしれませんが、折り畳むfoldLeft
別の方法があります。
def foldr[A, B](l: List[A], acc0: B, f: (A, B) => B): B = l match {
case Nil => acc0
case x :: xs => f(x, foldr(xs, acc0, f))
}
右側から始まる関数List(1, 2)
を適用してリストを折りたたんだとします - 実際には左側からリストを分解します!
f(1, f(2, Nil))
これは、リストの長さを計算するために使用できます。与えられたList(1, 2)
:
foldr(List(1, 2), 0, (i: Int, acc: Int) => 1 + acc)
// returns 2
これを使用して、別のリストを作成することもできます。
foldr[Int, List[Int]](List(1, 2), List[Int](), _ :: _)
//List[Int] = List(1, 2)
したがって、空のリストと::
関数を指定すると、別のリストを作成できました。要素が何らかのコンテキストにある場合はどうなるでしょうか? コンテキストが適用可能である場合でも、要素を::
そのコンテキストで適用できます。List(1, 2)
andを applicative として続けOption
ます。コンテキストで関数some(List[Int]()))
を適用することから始めます。これが の機能です。コンテキストで2 つの値を取り、提供された 2 つの引数の関数をコンテキストに入れ、それらを一緒に適用します。::
Option
F.map2
Option
Option
だから私たちが持っている文脈の外で(2, Nil) => 2 :: Nil
コンテキストでは、次のようになります。(Some(2), Some(Nil)) => Some(2 :: Nil)
元の質問に戻ります。
// do a foldr
DList.fromList(l).foldr(F.point(List[B]())) {
// starting with an empty list in its applicative context F.point(List[B]())
(a, fbs) => F.map2(f(a), fbs)(_ :: _)
// Apply the `::` function to the two values in the context
}
なぜ違いDList
が使われるのかわかりません。私が見ているのは、トランポリンを使用しているため、スタックを吹き飛ばすことなくこの実装が機能することを願っていますが、試したことがないのでわかりません。
このように正しい折り畳みを実装することの興味深い部分は、カタモルフィズムを使用して代数的データ型のトラバースを実装するアプローチが得られると思うことです。
たとえば、次のようになります。
trait Tree[+A]
object Leaf extends Tree[Nothing]
case class Node[A](a: A, left: Tree[A], right: Tree[A]) extends Tree[A]
Fold は次のように定義されます (実際には for と同じアプローチに従っていますList
):
def fold[A, B](tree: Tree[A], valueForLeaf: B, functionForNode: (A, B, B) => B): B = {
tree match {
case Leaf => valueForLeaf
case Node(a, left, right) => functionForNode(a,
fold(left, valueForLeaf, functionForNode),
fold(right, valueForLeaf, functionForNode)
)
}
}
traverse はそれをfold
withで使用しF.point(Leaf)
、 to に適用しますNode.apply
。ないF.map3
ので少し面倒かもしれませんが。