Аппликатив позволяет применять функцию в контексте к значению в контексте.Например, вы можете применить some((i: Int) => i + 1)
к some(3)
и получить some(4)
.Давайте забудем это сейчас.Я вернусь к этому позже.
Список имеет два представления: либо 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)
и Option
в качестве нашего аппликатива.Мы начинаем с some(List[Int]()))
, мы хотим применить функцию ::
в контексте 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 будет определен следующим образом (который действительно следует тому же подходу, что и для 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
с F.point(Leaf)
и применить его к Node.apply
.Хотя F.map3
нет, так что это может быть немного громоздко.