2010-04-01 11 views
17

Questa domanda non è intesa come esca di fiamma! Come potrebbe essere evidente, ho guardato allo Scalaz di recente. Sto cercando di capire why Ho bisogno di alcune delle funzionalità fornite dalla libreria. Ecco qualcosa:Scalaz: richiesta per caso d'uso per composizione Cokleisli

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 (parte: cosa avrei fatto se stavo cercando di evitare effetti collaterali come quello?). 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) } 

Allora io li uniscono 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 numero di caratteri degli elementi (String) nel NEL finale. Mi vengono in mente due cose:

  1. Come potevo sapere che il mio NEL sarebbe stato ridotto in questo modo dalle firme del metodo in questione? (Non mi aspettavo il risultato per tutto)
  2. Qual è il punto di questo? Per me può essere distillato un caso d'uso ragionevolmente semplice e facile da seguire?

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

risposta

18

Per comprendere il risultato, è necessario comprendere l'istanza Comonad[NonEmptyList]. Comonad[W] prevede sostanzialmente 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 alcuni container W che ha un elemento distinto "testa" (copure) e un modo di esporre la struttura interna del contenitore in modo da ottenere un contenitore per elemento (cojoin), ognuno con un determinato elemento in testa.

Il modo in cui questo viene implementato per NonEmptyList è che copure restituisce il capo 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 comporterà due funzioni f: W[A] => B e g: W[B] => C, non sapendo nulla su di loro oltre a quello W è un Comonad? Il tipo di input di f e il tipo di output di g non sono compatibili.Tuttavia, è possibile map(f) ottenere W[W[A]] => W[B] e quindi comporlo con g. Ora, dato un W[A], è possibile per ottenere il W[W[A]] da inserire in quella funzione. Quindi, l'unica composizione ragionevole è una funzione k che fa la 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 
+0

Grazie per questa risposta - Ho accettato * retronym * perché ha risposto alla richiesta di un caso d'uso ma questo è ancora ottimo! –

+0

Nessun problema, ho modificato la risposta del retronym per aggiungere un esempio di composizione cokleisli. – Apocalisp

+0

Credo che "puoi' map (f) 'per ottenere' W [W [A]] => W [B] '", purtroppo non ho diritti di modifica. –

9

Cojoin è definito anche per scalaz.Tree e scalaz.TreeLoc. Questo può essere exploited per trovare un flusso di tutti i percorsi dalla radice dell'albero a ciascun nodo foglia.

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

Utilizzando composizione freccia coKleisli, 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.