Domanda

Questa domanda non è da intendersi come fiamma esca! Come potrebbe essere apparente, ho cercato a Scalaz di recente. Sto cercando di capire perché Ho bisogno di alcune delle funzionalità che la biblioteca offre. Qui di qualcosa:

import scalaz._
import Scalaz._
type NEL[A] = NonEmptyList[A]
val NEL = NonEmptyList

ho messo alcune dichiarazioni println nelle mie funzioni per vedere cosa stava succedendo ( a parte: cosa avrei fatto se stavo cercando di evitare gli effetti collaterali del genere ?). Le mie funzioni sono:

val f: NEL[Int] => String    = (l: NEL[Int]) => {println("f: " + l); l.toString |+| "X" }
val g: NEL[String] => BigInt = (l: NEL[String]) => {println("g: " + l);  BigInt(l.map(_.length).sum) }

Poi io li combino tramite un cokleisli e passare in un NEL[Int]

val k = cokleisli(f) =>= cokleisli(g)
println("RES: "  + k( NEL(1, 2, 3) ))

Che cosa significa questa stampa?

f: NonEmptyList(1, 2, 3)
f: NonEmptyList(2, 3)
f: NonEmptyList(3)
g: NonEmptyList(NonEmptyList(1, 2, 3)X, NonEmptyList(2, 3)X, NonEmptyList(3)X)
RES: 57

Il valore RES è il conteggio carattere degli elementi (stringa) nella NEL finale. Due cose mi vengono in mente:

  1. Come potevo sapere che il mio NEL stava per essere ridotta in questo modo dalle firme dei metodi coinvolti? (Non mi aspettavo il risultato a tutti )
  2. Qual è il punto di questo? Può un facile da seguire utilizzo caso ragionevolmente semplice e distillare per me?

Questa domanda è un appello malcelato per qualche bella persona come retronym di spiegare come questo potente libreria funziona realmente.

È stato utile?

Soluzione

Per capire il risultato, è necessario comprendere l'istanza Comonad[NonEmptyList]. Comonad[W] fornisce essenzialmente tre funzioni (l'interfaccia attuale in Scalaz è un po 'diverso, ma questo aiuta con la spiegazione):

map:    (A => B) => W[A] => W[B]
copure: W[A] => A
cojoin: W[A] => W[W[A]]

Quindi, Comonad fornisce un'interfaccia per alcune W contenitore che ha un elemento distinto "testa" (copure) e un modo di esporre la struttura interna del contenitore in modo da ottenere un contenitore per ogni elemento (cojoin), ciascuno con una dato elemento alla testa.

Il modo in cui questo viene implementato per NonEmptyList è che copure restituisce la testa della lista, e cojoin restituisce una lista di liste, con questo elenco alla testa e tutte le code di questa lista in coda.

Esempio (sto accorciando NonEmptyList a Nel):

Nel(1,2,3).copure = 1
Nel(1,2,3).cojoin = Nel(Nel(1,2,3),Nel(2,3),Nel(3))

La funzione =>= è la composizione coKleisli. Come si comporre due funzioni f: W[A] => B e g: W[B] => C, sapendo nulla di loro diverso da quello W è un Comonad? Il tipo di ingresso di f e il tipo di uscita di g non sono compatibili. Tuttavia, è possibile map(f) per ottenere W[W[A]] => W[B] e poi comporre quello con g. Ora, dato un W[A], è possibile cojoin per ottenere il W[W[A]] per alimentare quella funzione. Così, la composizione unica ragionevole è un k funzione che fa il seguente:

k(x) = g(x.cojoin.map(f))

Quindi, per la vostra lista non vuota:

g(Nel(1,2,3).cojoin.map(f))
= g(Nel(Nel(1,2,3),Nel(2,3),Nel(3)).map(f))
= g(Nel("Nel(1,2,3)X","Nel(2,3)X","Nel(3)X"))
= BigInt(Nel("Nel(1,2,3)X","Nel(2,3)X","Nel(3)X").map(_.length).sum)
= BigInt(Nel(11,9,7).sum)
= 27

Altri suggerimenti

Cojoin è definito anche per scalaz .TREE e scalaz. TreeLoc . Questo può essere sfruttato per trovare un flusso di tutti i percorsi dalla radice dell'albero ad ogni nodo foglia.

def leafPaths[T](tree: Tree[T]): Stream[Stream[T]]
  = tree.loc.cojoin.toTree.flatten.filter(_.isLeaf).map(_.path)

Utilizzando coKleisli sulla composizione, siamo in grado di fare questo, per esempio:

def leafDist[A] = (cokleisli(leafPaths[A]) &&& cokleisli(_.rootLabel))
  =>= (_.map(s => (s._2, s._1.map(_.length).max)))

leafDist prende un albero e restituisce una copia di esso con ogni nodo annotato con la sua distanza massima da una foglia.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top