Un applicativo consente di applicare una funzione in un contesto a un valore in un contesto. Ad esempio, è possibile applicare some((i: Int) => i + 1)
a some(3)
e ottenere some(4)
. Dimentichiamo quello per ora. Tornerò su dopo.
L'elenco ha due rappresentazioni, è Nil
o head :: tail
. Si può essere utilizzato per ripiegare su di esso utilizzando foldLeft
Ma c'è un altro modo per piegare su di esso:
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))
}
Dato List(1, 2)
pieghiamo sulla lista applicando la funzione a partire dal lato destro - anche se abbiamo davvero decostruire la lista dalla parte di sinistra!
f(1, f(2, Nil))
Questo può essere utilizzato per calcolare la lunghezza di un elenco. Dato List(1, 2)
:
foldr(List(1, 2), 0, (i: Int, acc: Int) => 1 + acc)
// returns 2
Questo può essere utilizzato anche per creare un altro elenco:
foldr[Int, List[Int]](List(1, 2), List[Int](), _ :: _)
//List[Int] = List(1, 2)
Quindi dato un elenco vuoto e la funzione ::
siamo stati in grado di creare un altro elenco.Cosa succede se i nostri elementi si trovano in uno contesto? Se il nostro contesto è un applicativo, allora possiamo ancora applicare i nostri elementi e ::
in quel contesto. Proseguendo con List(1, 2)
e Option
come nostro applicativo. Iniziamo con some(List[Int]()))
per applicare la funzione ::
nel contesto Option
. Questo è ciò che fa F.map2
. Prende due valori nel loro contesto Option
, inserisce la funzione fornita di due argomenti nel contesto Option
e li applica insieme.
Quindi al di fuori del contesto abbiamo (2, Nil) => 2 :: Nil
Nel contesto abbiamo: (Some(2), Some(Nil)) => Some(2 :: Nil)
Tornando alla domanda iniziale:
// 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
}
io non sono sicuro perché è utilizzata la differenza DList
. Quello che vedo è che usa trampolini così speranzosi che faccia funzionare questa implementazione senza far esplodere lo stack, ma non l'ho provato quindi non lo so.
La parte interessante sull'implementazione della piega destra in questo modo è che penso che fornisca un approccio per implementare la traversa per i tipi di dati algebrici utilizzando catamorphisms.
Per esempio dato:
trait Tree[+A]
object Leaf extends Tree[Nothing]
case class Node[A](a: A, left: Tree[A], right: Tree[A]) extends Tree[A]
piegatura sarebbe definito come questo (che è in realtà seguendo lo stesso approccio per 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)
)
}
}
ed attraversare userebbero che fold
con F.point(Leaf)
e applicalo a Node.apply
. Sebbene non ci sia il numero F.map3
, potrebbe essere un po 'macchinoso.
risposta impressionante che non richiede alcuna conoscenza esterna – betehess