2010-04-01 14 views
17

¡Esta pregunta no es cebo de llama! Como podría ser evidente, he estado mirando Scalaz recientemente. Intento entender por qué Necesito algunas de las funciones que proporciona la biblioteca. Aquí hay algo:Scalaz: solicitud de uso para la composición de Cokleisli

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

os pongo unas declaraciones println en mis funciones para ver lo que estaba pasando (a un lado: ¿qué habría hecho si yo estaba tratando de evitar efectos secundarios como la que?). Mis funciones son:

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) } 

Entonces ellos combinan a través de un cokleisli y pasan en un NEL[Int]

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

Lo que hace esta impresión?

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 

El valor RES es el número de caracteres de los elementos (Cadena) en la NEL final. Dos cosas se me ocurren:

  1. ¿Cómo podría haber sabido que mi NEL iba a reducirse de esta manera a partir de las firmas de métodos implicadas? (No esperaba el resultado en total)
  2. ¿Qué sentido tiene esto? ¿Se puede destilar para mí un caso de uso razonablemente simple y fácil de seguir?

Esta pregunta es una súplica apenas velado por alguna persona encantadora como retronym para explicar cómo esta poderosa biblioteca funciona realmente.

Respuesta

18

Para comprender el resultado, debe comprender la instancia Comonad[NonEmptyList]. Comonad[W] esencialmente proporciona tres funciones (la interfaz real en Scalaz es un poco diferente, pero esto ayuda con la explicación):

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

Así, Comonad proporciona una interfaz para algunos contenedores W que tiene un elemento distinguido "cabeza" (copure) y una forma de exponer la estructura interna del contenedor para que obtengamos un contenedor por elemento (cojoin), cada uno con un elemento dado en la cabecera.

La forma en que esto se implementa para NonEmptyList es que copure devuelve la cabeza de la lista, y cojoin devuelve una lista de listas, con esta lista en la cabeza y todas las colas de esta lista en la cola.

Ejemplo (estoy acortando 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 función =>= es la composición coKleisli. ¿Cómo redactaría dos funciones f: W[A] => B y g: W[B] => C, sin saber nada sobre ellas aparte de que W es un Comonad? El tipo de entrada f y el tipo de salida g no son compatibles.Sin embargo, puede map(f) obtener W[W[A]] => W[B] y luego componer eso con g. Ahora, dado un W[A], puede cojoin obtener el W[W[A]] para alimentar esa función. Por lo tanto, la única composición razonable es una función k que hace lo siguiente:

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

Así que para su lista no vacía:

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

Gracias por esta respuesta - He aceptado * retronym's * porque ha respondido a la solicitud de un caso de uso, pero este sigue siendo excelente. –

+0

No hay problema, he editado la respuesta de retronym para agregar un ejemplo de composición de cokleisli. – Apocalisp

+0

Creo que "no se puede' mapear (f) 'para obtener' W [W [A]] => W [B] '", ¡ay !, no tengo derechos de edición. –

9

Cojoin también se define por scalaz.Tree y scalaz.TreeLoc. Puede ser exploited para encontrar una secuencia de todas las rutas desde la raíz del árbol hasta cada nodo hoja.

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

Usando coKleisli composición flecha, podemos hacer esto, por ejemplo:

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

leafDist tiene un árbol y devuelve una copia de la misma con cada nodo anotado con su distancia máxima de una hoja.

Cuestiones relacionadas