verdad tabla suma numeros complejos ciclos haskell monads free-monad comonad

tabla - ¿Cuáles son algunos ejemplos motivadores para Cofree CoMonad en Haskell?



suma en haskell (1)

He estado jugando con Cofree , y no puedo asimilarlo.

Por ejemplo, quiero jugar con Cofree [] Num en ghci y no puedo obtener ningún ejemplo interesante.

Por ejemplo, si construyo un tipo Cofree:

let a = 1 :< [2, 3]

Esperaría extract a == 1 , pero en cambio me sale este error:

No instance for (Num (Cofree [] a0)) arising from a use of ‘it’ In the first argument of ‘print’, namely ‘it’ In a stmt of an interactive GHCi command: print it

Y un tipo de:

extract a :: (Num a, Num (Cofree [] a)) => a

¿Puedo obtener algunos ejemplos simples, incluso triviales, de cómo usar Cofree con, por ejemplo, functors: [] , o Maybe , o Either , that demuestra

  • extract
  • extend
  • unwrap
  • duplicate ?

Cross Publicado: https://www.reddit.com/r/haskell/comments/4wlw70/what_are_some_motivating_examples_for_cofree/

EDITAR: Guiados por el comentario de David Young, aquí hay algunos ejemplos mejores que muestran que mis primeros intentos fueron equivocados, sin embargo, aún me encantan algunos ejemplos que pueden guiar la intuición de Cofree:

> let a = 1 :< [] > extract a 1 > let b = 1 :< [(2 :< []), (3 :< [])] > extract b 1 > unwrap b [2 :< [],3 :< []] > map extract $ unwrap b [2,3]


Recapitulemos la definición del tipo de datos Cofree .

data Cofree f a = a :< f (Cofree f a)

Eso es al menos suficiente para diagnosticar el problema con el ejemplo. Cuando escribiste

1 :< [2, 3]

cometió un pequeño error que se informó de forma más sutil que útil. Aquí, f = [] y a es algo numérico, porque 1 :: a . En consecuencia, necesitas

[2, 3] :: [Cofree [] a]

y por lo tanto

2 :: Cofree [] a

lo que podría estar bien si Cofree [] a también fuera Cofree [] a instancia de Num . Su definición adquiere así una restricción que es poco probable que se satisfaga, y de hecho, cuando usa su valor, el intento de satisfacer la restricción falla.

Intenta nuevamente con

1 :< [2 :< [], 3 :< []]

y deberías tener mejor suerte.

Ahora, veamos qué tenemos. Comience por mantenerlo simple. ¿Qué es Cofree f () ? ¿Qué es, en particular, Cofree [] () ? Este último es isomorfo al punto fijo de [] : las estructuras de los árboles donde cada nodo es una lista de subárboles, también conocidos como "rosales no etiquetados". P.ej,

() :< [ () :< [ () :< [] , () :< [] ] , () :< [] ]

Del mismo modo, Cofree Maybe () es más o menos el punto fijo de Maybe : una copia de los números naturales, porque Maybe nos da una posición cero o una posición para conectar un subárbol.

zero :: Cofree Maybe () zero = () :< Nothing succ :: Cofree Maybe () -> Cofree Maybe () succ n = () :< Just n

Un caso trivial importante es Cofree (Const y) () , que es una copia de y . El functor Const y no da posiciones para los subárboles.

pack :: y -> Cofree (Const y) () pack y = () :< Const y

Luego, vamos a ocuparnos del otro parámetro. Te dice qué tipo de etiqueta adjuntas a cada nodo. Cambiar el nombre de los parámetros de manera más sugestiva

data Cofree nodeOf label = label :< nodeOf (Cofree nodeOf label)

Cuando etiquetamos el ejemplo (Const y) , obtenemos pares

pair :: x -> y -> Cofree (Const y) x pair x y = x :< Const y

Cuando adjuntamos etiquetas a los nodos de nuestros números, obtenemos listas no vacías

one :: x -> Cofree Maybe x one = x :< Nothing cons :: x -> Cofree Maybe x -> Cofree Maybe x cons x xs = x :< Just xs

Y para las listas, nos etiquetan los rosales.

0 :< [ 1 :< [ 3 :< [] , 4 :< [] ] , 2 :< [] ]

Estas estructuras son siempre "no vacías", porque hay al menos un nodo superior, incluso si no tiene hijos, y ese nodo siempre tendrá una etiqueta. La operación de extract le da la etiqueta del nodo superior.

extract :: Cofree f a -> a extract (a :< _) = a

Es decir, extract elimina el contexto de la etiqueta superior.

Ahora, la operación duplicate decora cada etiqueta con su propio contexto.

duplicate :: Cofree f a -> Cofree f (Cofree f a) duplicate a :< fca = (a :< fca) :< fmap duplicate fca -- f''s fmap

Podemos obtener una instancia de Functor para Cofree f visitando todo el árbol

fmap :: (a -> b) -> Cofree f a -> Cofree f b fmap g (a :< fca) = g a :< fmap (fmap g) fca -- ^^^^ ^^^^ -- f''s fmap |||| -- (Cofree f)''s fmap, used recursively

No es difícil ver eso

fmap extract . duplicate = id

porque el duplicate decora cada nodo con su contexto, luego el fmap extract descarta la decoración.

Tenga en cuenta que fmap solo mira las etiquetas de la entrada para calcular las etiquetas de la salida. Supongamos que queremos calcular las etiquetas de salida según cada etiqueta de entrada en su contexto . Por ejemplo, dado un árbol no etiquetado, podríamos etiquetar cada nodo con el tamaño de su subárbol completo. Gracias a la instancia Foldable para Cofree f , deberíamos poder contar nodos con.

length :: Foldable f => Cofree f a -> Int

Entonces eso significa

fmap length . duplicate :: Cofree f a -> Cofree f Int

La idea clave de Comonads es capturar "cosas con cierto contexto", y te permiten aplicar mapas dependientes del contexto en cualquier lugar.

extend :: Comonad c => (c a -> b) -> c a -> c b extend f = fmap f -- context-dependent map everywhere . -- after duplicate -- decorating everything with its context

Definir extend más directamente le ahorra el problema de la duplicación (aunque eso solo equivale a compartir).

extend :: (Cofree f a -> b) -> Cofree f a -> Cofree f b extend g ca@(_ :< fca) = g ca :< fmap (extend g) fca

Y puedes obtener duplicate tomando

duplicate = extend id -- the output label is the input label in its context

Además, si selecciona extract como lo que debe hacer para cada etiqueta en contexto, simplemente coloca cada etiqueta en su lugar original:

extend extract = id

Estas "operaciones en etiquetas en contexto" se llaman "flechas co-Kleisli",

g :: c a -> b

y el trabajo de extend es interpretar una flecha co-Kleisli como una función en estructuras enteras. La operación de extract es la flecha de co-Kleisli de identidad, y se interpreta por extend como la función de identidad. Por supuesto, hay una composición co-Kleisli

(=<=) :: Comonad c => (c s -> t) -> (c r -> s) -> (c r -> t) (g =<= h) = g . extend h

y las leyes de comonad aseguran que =<= es asociativo y absorbe el extract , dándonos la categoría co-Kleisli. Además, tenemos

extend (g =<= h) = extend g . extend h

de modo que extend es un funtor (en el sentido categórico) de la categoría co-Kleisli a sets-and-functions. Estas leyes no son difíciles de verificar para Cofree , ya que siguen las leyes de Functor para la forma del nodo.

Ahora, una forma útil de ver una estructura en una comonad de cofres es como una especie de "servidor de juegos". Una estructura

a :< fca

representa el estado del juego. Un movimiento en el juego consiste en "detener", en cuyo caso obtienes la a , o "continuar", al elegir un subárbol de fca . Por ejemplo, considere

Cofree ((->) move) prize

Un cliente para este servidor debe detenerse o continuar dando un move : es una lista de move . El juego se juega de la siguiente manera:

play :: [move] -> Cofree ((->) move) prize -> prize play [] (prize :< _) = prize play (m : ms) (_ :< f) = play ms (f m)

Tal vez un move es un Char y el prize es el resultado de analizar la secuencia de caracteres.

Si miras con suficiente atención, verás que [move] es una versión de Free ((,) move) () . Las mónadas libres representan estrategias del cliente. El ((,) move) functor ((,) move) equivale a una interfaz de comando con solo el comando "enviar un move ". El functor ((->) move) es la estructura correspondiente "responder al envío de un move ".

Algunos funtores se pueden ver como capturar una interfaz de comando; la mónada libre para dicho functor representa programas que hacen comandos; el funtor tendrá un "doble" que representa cómo responder a los comandos; el cofre comonad del dual es la noción general de entorno en el que se pueden ejecutar los programas que hacen los comandos, con la etiqueta que dice qué hacer si el programa se detiene y devuelve un valor, y las subestructuras que dicen cómo continuar ejecutando el programa si emite un comando.

Por ejemplo,

data Comms x = Send Char x | Receive (Char -> x)

describe que se le permite enviar o recibir caracteres. Su doble es

data Responder x = Resp {ifSend :: Char -> x, ifReceive :: (Char, x)}

Como ejercicio, mira si puedes implementar la interacción

chatter :: Free Comms x -> Cofree Responder y -> (x, y)