¿Qué significa "coalgebra" en el contexto de la programación?


339

He escuchado el término "coalgebras" varias veces en la programación funcional y los círculos PLT, especialmente cuando la discusión es sobre objetos, comonads, lentes y demás. Buscar en Google este término ofrece páginas que ofrecen una descripción matemática de estas estructuras, lo cual me resulta bastante incomprensible. ¿Alguien puede explicar qué significan las coalgebras en el contexto de la programación, cuál es su importancia y cómo se relacionan con los objetos y los comonads?


21
¿Puedo recomendar el excelente libro de Jeremy Gibbons Patterns in FP: patternsinfp.wordpress.com y su artículo bastante comprensible "Calculating Functional Programs"? Ambos cubren coalgebras de una manera bastante rigurosa (en comparación con, por ejemplo, una publicación de blog), pero también son bastante independientes para alguien que conoce un poco de Haskell.
Kristopher Micinski

2
@KristopherMicinski, muy interesante. ¡Gracias!
missingfaktor

Respuestas:


474

Álgebras

Creo que el lugar para comenzar sería entender la idea de un álgebra . Esto es solo una generalización de estructuras algebraicas como grupos, anillos, monoides, etc. La mayoría de las veces, estas cosas se introducen en términos de conjuntos, pero como estamos entre amigos, hablaré sobre los tipos de Haskell. (Sin embargo, no puedo resistirme a usar algunas letras griegas, ¡hacen que todo se vea mejor!)

Un álgebra, entonces, es solo un tipo τcon algunas funciones e identidades. Estas funciones toman diferentes números de argumentos de tipo τy producen un τ: no cursivo, todos parecen (τ, τ,…, τ) → τ. También pueden tener "identidades", elementos τque tienen un comportamiento especial con algunas de las funciones.

El ejemplo más simple de esto es el monoide. Un monoide es cualquier tipo τcon una función mappend ∷ (τ, τ) → τy una identidad mzero ∷ τ. Otros ejemplos incluyen cosas como grupos (que son como monoides, excepto con una invert ∷ τ → τfunción adicional ), anillos, celosías, etc.

Todas las funciones funcionan τpero pueden tener aridades diferentes. Podemos escribir esto como τⁿ → τ, donde se τⁿasigna a una tupla de n τ. De esta manera, tiene sentido pensar en las identidades como τ⁰ → τdónde τ⁰está solo la tupla vacía (). Así que podemos simplificar la idea de un álgebra ahora: es solo un tipo con algunas funciones.

Un álgebra es solo un patrón común en matemáticas que se ha "factorizado", tal como lo hacemos con el código. La gente notó que un montón de cosas interesantes —los monoides, grupos, celosías, etc. mencionados anteriormente— siguen un patrón similar, por lo que lo resumieron. La ventaja de hacer esto es la misma que en la programación: crea pruebas reutilizables y facilita ciertos tipos de razonamiento.

Álgebras F

Sin embargo, no hemos terminado con el factoring. Hasta ahora, tenemos un montón de funciones τⁿ → τ. De hecho, podemos hacer un buen truco para combinarlos en una sola función. En particular, echemos un vistazo a los monoides: tenemos mappend ∷ (τ, τ) → τy mempty ∷ () → τ. Podemos convertirlos en una sola función usando un tipo de suma— Either. Se vería así:

op  Monoid τ  Either (τ, τ) ()  τ
op (Left (a, b)) = mappend (a, b)
op (Right ())    = mempty

De hecho, podemos usar esta transformación repetidamente para combinar todas las τⁿ → τfunciones en una sola, para cualquier álgebra. (De hecho, podemos hacer esto para cualquier número de funciones a → τ, b → τy así sucesivamente para cualquiera a, b,… ).

Esto nos permite hablar sobre álgebras como un tipo τcon una sola función desde un lío de Eithers a un solo τ. Para monoides, este desastre es: Either (τ, τ) (); para los grupos (que tienen un extra de τ → τfuncionamiento), es: Either (Either (τ, τ) τ) (). Es un tipo diferente para cada estructura diferente. Entonces, ¿qué tienen en común todos estos tipos? Lo más obvio es que todos son solo sumas de productos: tipos de datos algebraicos. Por ejemplo, para monoides, podríamos crear un tipo de argumento monoide que funcione para cualquier monoide τ:

data MonoidArgument τ = Mappend τ τ -- here τ τ is the same as (τ, τ)
                      | Mempty      -- here we can just leave the () out

Podemos hacer lo mismo para grupos, anillos, celosías y todas las demás estructuras posibles.

¿Qué más hay de especial en todos estos tipos? ¡Pues todos lo son Functors! P.ej:

instance Functor MonoidArgument where
  fmap f (Mappend τ τ) = Mappend (f τ) (f τ)
  fmap f Mempty        = Mempty

Entonces podemos generalizar nuestra idea de un álgebra aún más. Es solo un tipo τcon una función f τ → τpara algún functor f. De hecho, podríamos escribir esto como una clase de tipo:

class Functor f  Algebra f τ where
  op  f τ  τ

Esto a menudo se denomina "álgebra F" porque lo determina el functor F. Si pudiéramos aplicar parcialmente las clases de tipos, podríamos definir algo así class Monoid = Algebra MonoidArgument.

Coalgebras

Ahora, espero que comprenda bien qué es un álgebra y cómo es solo una generalización de las estructuras algebraicas normales. Entonces, ¿qué es un F-coalgebra? Bueno, el co implica que es el "dual" de un álgebra, es decir, tomamos un álgebra y volteamos algunas flechas. Solo veo una flecha en la definición anterior, así que solo voltearé eso:

class Functor f  CoAlgebra f τ where
  coop  τ  f τ

¡Y eso es todo! Ahora, esta conclusión puede parecer un poco frívola (je). Le dice qué es un coalgebra, pero en realidad no da una idea de cómo es útil o por qué nos importa. Llegaré a eso en un momento, una vez que encuentre o encuentre un buen ejemplo o dos: P.

Clases y objetos

Después de leer un poco, creo que tengo una buena idea de cómo usar coalgebras para representar clases y objetos. Tenemos un tipo Cque contiene todos los posibles estados internos de los objetos en la clase; la clase en sí misma es una coalgebra sobre la Ccual se especifican los métodos y propiedades de los objetos.

Como se muestra en el ejemplo de álgebra, si tenemos un montón de funciones como a → τy b → τpara cualquiera a, b,…, podemos combinarlas todas en una sola función usando Eitherun tipo de suma. La "noción" dual sería combinar un montón de funciones de tipo τ → a, τ → by así sucesivamente. Podemos hacer esto usando el doble de un tipo de suma: un tipo de producto. Entonces, dadas las dos funciones anteriores (llamadas fy g), podemos crear una sola de esta manera:

both  τ  (a, b)
both x = (f x, g x)

El tipo (a, a)es un functor de forma directa, por lo que sin duda encaja con nuestra noción de un F-coalgebra. Este truco en particular nos permite agrupar un montón de funciones diferentes, o, para OOP, métodos, en una sola función de tipo τ → f τ.

Los elementos de nuestro tipo Crepresentan el estado interno del objeto. Si el objeto tiene algunas propiedades legibles, deben poder depender del estado. La forma más obvia de hacer esto es hacer que funcionen C. Entonces, si queremos una propiedad de longitud (por ejemplo object.length), tendríamos una función C → Int.

Queremos métodos que puedan tomar un argumento y modificar el estado. Para hacer esto, necesitamos tomar todos los argumentos y producir uno nuevo C. Imaginemos un setPositionmétodo que toma una xy una yde coordenadas: object.setPosition(1, 2). Se vería así: C → ((Int, Int) → C).

El patrón importante aquí es que los "métodos" y las "propiedades" del objeto toman el objeto mismo como su primer argumento. Esto es como el selfparámetro en Python y como el implícito thisde muchos otros lenguajes. Un coalgebra esencialmente sólo encapsula el comportamiento de tomar un selfparámetro: eso es lo que la primera Cen C → F Ces.

Así que vamos a poner todo junto. Imaginemos una clase con una positionpropiedad, una namepropiedad y una setPositionfunción:

class C
  private
    x, y  : Int
    _name : String
  public
    name        : String
    position    : (Int, Int)
    setPosition : (Int, Int)  C

Necesitamos dos partes para representar esta clase. Primero, necesitamos representar el estado interno del objeto; en este caso solo tiene dos Intsy a String. (Este es nuestro tipo C). Entonces tenemos que llegar a la coalgebra que represente a la clase.

data C = Obj { x, y   Int
             , _name  String }

Tenemos dos propiedades para escribir. Son bastante triviales:

position  C  (Int, Int)
position self = (x self, y self)

name  C  String
name self = _name self

Ahora solo necesitamos poder actualizar la posición:

setPosition  C  (Int, Int)  C
setPosition self (newX, newY) = self { x = newX, y = newY }

Esto es como una clase de Python con sus selfvariables explícitas . Ahora que tenemos un montón de self →funciones, necesitamos combinarlas en una sola función para el coalgebra. Podemos hacer esto con una simple tupla:

coop  C  ((Int, Int), String, (Int, Int)  C)
coop self = (position self, name self, setPosition self)

El tipo ((Int, Int), String, (Int, Int) → c)-por cualquier c -es un funtor, por lo que cooptiene la forma que queremos: Functor f ⇒ C → f C.

Dado esto, Cjunto con coopformar un coalgebra que especifica la clase que di arriba. Puede ver cómo podemos usar esta misma técnica para especificar cualquier número de métodos y propiedades para que nuestros objetos tengan.

Esto nos permite usar el razonamiento coalgebraico para tratar con las clases. Por ejemplo, podemos incorporar la noción de un "homomorfismo de F-coalgebra" para representar transformaciones entre clases. Este es un término que suena aterrador y solo significa una transformación entre coalgebras que preserva la estructura. Esto hace que sea mucho más fácil pensar en asignar clases a otras clases.

En resumen, un F-coalgebra representa una clase al tener un conjunto de propiedades y métodos que dependen de un selfparámetro que contiene el estado interno de cada objeto.

Otras categorias

Hasta ahora, hemos hablado de álgebras y coalgebras como los tipos de Haskell. Un álgebra es solo un tipo τcon una función f τ → τy un coalgebra es solo un tipo τcon una función τ → f τ.

Sin embargo, nada vincula realmente estas ideas con Haskell per se . De hecho, generalmente se introducen en términos de conjuntos y funciones matemáticas en lugar de tipos y funciones de Haskell. De hecho, podemos generalizar estos conceptos a cualquier categoría.

Podemos definir un F-álgebra para alguna categoría C. Primero, necesitamos un functor F : C → C, es decir, un endofunctor . (Todo Haskell Functors son en realidad endofunctors de Hask → Hask.) Entonces, un álgebra es sólo un objeto Aa partir Cde un morfismo F A → A. Un coalgebra es lo mismo excepto con A → F A.

¿Qué ganamos al considerar otras categorías? Bueno, podemos usar las mismas ideas en diferentes contextos. Como mónadas. En Haskell, una mónada es de algún tipo M ∷ ★ → ★con tres operaciones:

map         β)  (M α  M β)
return    α  M α
join      M (M α)  M α

La mapfunción es solo una prueba del hecho de que Mes a Functor. Entonces, podemos decir que una mónada es solo un functor con dos operaciones: returny join.

Los functores forman una categoría ellos mismos, con morfismos entre ellos llamados "transformaciones naturales". Una transformación natural es solo una forma de transformar un functor en otro mientras preserva su estructura. Aquí hay un buen artículo que ayuda a explicar la idea. Habla de eso concat, que es solo joinpara listas.

Con los functores Haskell, la composición de dos functores es un functor en sí mismo. En pseudocódigo, podríamos escribir esto:

instance (Functor f, Functor g)  Functor (f  g) where
  fmap fun x = fmap (fmap fun) x

Esto nos ayuda a pensar joincomo un mapeo de f ∘ f → f. El tipo de joines ∀α. f (f α) → f α. Intuitivamente, podemos ver cómo una función válida para todos los tipos αpuede considerarse como una transformación de f.

returnEs una transformación similar. Su tipo es ∀α. α → f α. Esto se ve diferente: ¡el primero αno está "en" un functor! Afortunadamente, podemos solucionar este problema mediante la adición de un funtor identidad no: ∀α. Identity α → f α. Entonces returnes una transformación Identity → f.

Ahora podemos pensar en una mónada como un álgebra basada en algún functor fcon operaciones f ∘ f → fy Identity → f. ¿No te parece familiar? Es muy similar a un monoide, que era solo un tipo τde operaciones τ × τ → τy () → τ.

Entonces, una mónada es como un monoide, excepto que en lugar de tener un tipo, tenemos un functor. Es el mismo tipo de álgebra, solo que en una categoría diferente. (Aquí es donde la frase "Una mónada es solo un monoide en la categoría de endofunctores" proviene de lo que yo sé).

Ahora, tenemos estas dos operaciones: f ∘ f → fy Identity → f. Para obtener el coalgebra correspondiente, simplemente volteamos las flechas. Esto nos da dos nuevas operaciones: f → f ∘ fy f → Identity. Podemos convertirlos en tipos de Haskell agregando variables de tipo como arriba, dándonos ∀α. f α → f (f α)y ∀α. f α → α. Esto se parece a la definición de comonad:

class Functor f  Comonad f where
  coreturn  f α  α
  cojoin    f α  f (f α)

Entonces, una comonad es una coalgebra en una categoría de endofunctores.


45
Esto es increíblemente valioso. Me las arreglé para aclarar algunas intuiciones sobre todo este asunto de F-álgebra a partir de la lectura y ejemplos (por ejemplo, al ver su uso con catamoprismos), pero esto es todo claro incluso para mí. ¡Gracias!
Luis Casillas

28
Esta es una gran explicación.
Edward KMETT

55
@ EdwardKmett: Gracias. ¿Las cosas que agregué sobre clases y objetos están bien? Hoy solo leí sobre eso, pero parece tener sentido.
Tikhon Jelvis

77
Para lo que vale: la "categoría de endofunctores" aquí es más precisamente una categoría cuyos objetos son endofunctores en alguna categoría y cuyas flechas son transformaciones naturales. Esta es una categoría monoidal, con la composición del functor correspondiente (,)y la identidad del functor (). Un objeto monoide dentro de una categoría monoidal es un objeto con flechas correspondientes a su álgebra monoidal, que describe un objeto monoide en Hask con tipos de productos como la estructura monoidal. Un objeto monoide en la categoría de endofunctores en C es una mónada en C, así que sí, su comprensión es correcta. :]
CA McCann

8
¡Ese fue un final épico!
jdinunzio

85

F-álgebras y F-coalgebras son estructuras matemáticas que son instrumentales en el razonamiento sobre los tipos inductivos (o tipos recursivos ).

Álgebras F

Comenzaremos primero con F-álgebras. Intentaré ser lo más simple posible.

Supongo que sabes lo que es un tipo recursivo. Por ejemplo, este es un tipo para una lista de enteros:

data IntList = Nil | Cons (Int, IntList)

Es obvio que es recursivo; de hecho, su definición se refiere a sí misma. Su definición consta de dos constructores de datos, que tienen los siguientes tipos:

Nil  :: () -> IntList
Cons :: (Int, IntList) -> IntList

Tenga en cuenta que he escrito tipo de Nilcomo () -> IntList, no simplemente IntList. De hecho, estos son tipos equivalentes desde el punto de vista teórico, porque el ()tipo tiene solo un habitante.

Si escribimos firmas de estas funciones de una manera más teórica, obtendremos

Nil  :: 1 -> IntList
Cons :: Int × IntList -> IntList

donde 1es un conjunto de unidades (conjunto con un elemento) y la A × Boperación es un producto cruzado de dos conjuntos Ay B(es decir, un conjunto de pares (a, b)donde aatraviesa todos los elementos Ay batraviesa todos los elementos de B).

Unión disjunta de dos conjuntos Ay Bes un conjunto A | Bque es una unión de conjuntos {(a, 1) : a in A}y {(b, 2) : b in B}. En esencia se trata de un conjunto de todos los elementos de ambos Ay B, pero con cada uno de estos elementos 'marcado' como perteneciente a cualquiera de los dos Ao B, por lo que cuando tomamos cualquier elemento de A | Bsabremos de inmediato si este elemento proviene de Ao desde B.

Podemos 'unirnos' Nily Consfunciones, por lo que formarán una sola función trabajando en un conjunto 1 | (Int × IntList):

Nil|Cons :: 1 | (Int × IntList) -> IntList

De hecho, si la Nil|Consfunción se aplica al ()valor (que, obviamente, pertenece al 1 | (Int × IntList)conjunto), entonces se comporta como si lo fuera Nil; si Nil|Consse aplica a cualquier valor de tipo (Int, IntList)(tales valores también están en el conjunto 1 | (Int × IntList), se comporta como Cons.

Ahora considere otro tipo de datos:

data IntTree = Leaf Int | Branch (IntTree, IntTree)

Tiene los siguientes constructores:

Leaf   :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree

que también se puede unir en una función:

Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree

Se puede ver que ambas joinedfunciones tienen un tipo similar: ambas parecen

f :: F T -> T

donde Fes una especie de transformación que tiene nuestro tipo y da tipo más complejo, que consiste en xy |operaciones, usos de Ty posiblemente otros tipos. Por ejemplo, para IntListy IntTree Ftiene el siguiente aspecto:

F1 T = 1 | (Int × T)
F2 T = Int | (T × T)

Podemos notar de inmediato que cualquier tipo algebraico se puede escribir de esta manera. De hecho, es por eso que se les llama 'algebraicos': consisten en una cantidad de 'sumas' (uniones) y 'productos' (productos cruzados) de otros tipos.

Ahora podemos definir F-álgebra. F-álgebra es solo un par (T, f), donde Thay algún tipo y fes una función de tipo f :: F T -> T. En nuestros ejemplos, las álgebras F son (IntList, Nil|Cons)y (IntTree, Leaf|Branch). Tenga en cuenta, sin embargo, que a pesar de ese tipo de ffunción es la misma para cada F, Ty en fsí mismas pueden ser arbitrarias. Por ejemplo, (String, g :: 1 | (Int x String) -> String)o (Double, h :: Int | (Double, Double) -> Double)para algunos gy htambién son F-álgebras para F. correspondiente

Luego podemos introducir homomorfismos de álgebra F y luego álgebras F iniciales , que tienen propiedades muy útiles. De hecho, (IntList, Nil|Cons)es un álgebra F1 inicial, y (IntTree, Leaf|Branch)es un álgebra F2 inicial. No presentaré definiciones exactas de estos términos y propiedades, ya que son más complejos y abstractos de lo necesario.

No obstante, el hecho de que, digamos, (IntList, Nil|Cons)es F-álgebra nos permite definir una foldfunción similar a este tipo. Como sabe, fold es un tipo de operación que transforma algunos tipos de datos recursivos en un valor finito. Por ejemplo, podemos plegar una lista de enteros en un solo valor, que es la suma de todos los elementos de la lista:

foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10

Es posible generalizar dicha operación en cualquier tipo de datos recursivo.

La siguiente es una firma de foldrfunción:

foldr :: ((a -> b -> b), b) -> [a] -> b

Tenga en cuenta que he usado llaves para separar los dos primeros argumentos del último. Esta no es una foldrfunción real , pero es isomórfica (es decir, puede obtener fácilmente una de la otra y viceversa). Aplicado parcialmente foldrtendrá la siguiente firma:

foldr ((+), 0) :: [Int] -> Int

Podemos ver que esta es una función que toma una lista de enteros y devuelve un solo entero. Definamos dicha función en términos de nuestro IntListtipo.

sumFold :: IntList -> Int
sumFold Nil         = 0
sumFold (Cons x xs) = x + sumFold xs

Vemos que esta función consta de dos partes: la primera parte define el comportamiento de esta función por Nilparte de IntList, y la segunda parte define el comportamiento de la función por Consparte.

Ahora suponga que no estamos programando en Haskell sino en algún lenguaje que permita el uso de tipos algebraicos directamente en firmas de tipo (bueno, técnicamente Haskell permite el uso de tipos algebraicos a través de tuplas y Either a btipos de datos, pero esto conducirá a verbosidades innecesarias). Considere una función:

reductor :: () | (Int × Int) -> Int
reductor ()     = 0
reductor (x, s) = x + s

Se puede ver que reductores una función de tipo F1 Int -> Int, ¡como en la definición de F-álgebra! De hecho, el par (Int, reductor)es un álgebra de F1.

Debido a que IntListes una F1-álgebra inicial, para cada tipo Ty para cada función r :: F1 T -> Tno existe una función, llamada catamorphism para r, que convierte IntLista T, y tal función es única. De hecho, en nuestro ejemplo un catamorphism para reductores sumFold. Tenga en cuenta cómo reductory sumFoldson similares: ¡tienen casi la misma estructura! El uso del parámetro en reductordefinición s(cuyo tipo corresponde a T) corresponde al uso del resultado del cálculo de sumFold xsen sumFolddefinición.

Solo para hacerlo más claro y ayudarlo a ver el patrón, aquí hay otro ejemplo, y nuevamente comenzamos desde la función de plegado resultante. Considere la appendfunción que agrega su primer argumento al segundo argumento:

(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]

Así se ve en nuestro IntList:

appendFold :: IntList -> IntList -> IntList
appendFold ys ()          = ys
appendFold ys (Cons x xs) = x : appendFold ys xs

Nuevamente, intentemos escribir el reductor:

appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys ()      = ys
appendReductor ys (x, rs) = x : rs

appendFoldEs un catamorfismo para el appendReductorque se transforma IntListen IntList.

Entonces, esencialmente, las álgebras F nos permiten definir 'pliegues' en estructuras de datos recursivas, es decir, operaciones que reducen nuestras estructuras a algún valor.

F-coalgebras

F-coalgebras se denominan término 'dual' para F-álgebras. Nos permiten definir unfoldstipos de datos recursivos, es decir, una forma de construir estructuras recursivas a partir de algún valor.

Supongamos que tiene el siguiente tipo:

data IntStream = Cons (Int, IntStream)

Este es un flujo infinito de enteros. Su único constructor tiene el siguiente tipo:

Cons :: (Int, IntStream) -> IntStream

O, en términos de conjuntos

Cons :: Int × IntStream -> IntStream

Haskell le permite emparejar patrones en constructores de datos, para que pueda definir las siguientes funciones trabajando en IntStreams:

head :: IntStream -> Int
head (Cons (x, xs)) = x

tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs

Naturalmente, puede 'unir' estas funciones en una sola función de tipo IntStream -> Int × IntStream:

head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)

Observe cómo el resultado de la función coincide con la representación algebraica de nuestro IntStreamtipo. Algo similar también se puede hacer para otros tipos de datos recursivos. Quizás ya hayas notado el patrón. Me refiero a una familia de funciones de tipo

g :: T -> F T

donde Thay algun tipo De ahora en adelante definiremos

F1 T = Int × T

Ahora, F-coalgebra es un par (T, g), donde Tes un tipo y ges una función del tipo g :: T -> F T. Por ejemplo, (IntStream, head&tail)es un F1-coalgebra. De nuevo, al igual que en F-álgebras, gy Tpuede ser arbitrario, por ejemplo, (String, h :: String -> Int x String)también es un F1-coalgebra por alguna h.

Entre todas las F-coalgebras hay las llamadas F-coalgebras terminales , que son duales a las F-álgebras iniciales. Por ejemplo, IntStreames un terminal F-coalgebra. Esto significa que para cada tipo Ty para cada función p :: T -> F1 Texiste una función, llamada anamorfosis , que convierte Ta IntStream, y tal función es única.

Considere la siguiente función, que genera un flujo de enteros sucesivos a partir del dado:

nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))

Ahora inspeccionemos una función natsBuilder :: Int -> F1 Int, es decir natsBuilder :: Int -> Int × Int:

natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)

De nuevo, podemos ver cierta similitud entre natsy natsBuilder. Es muy similar a la conexión que hemos observado con reductores y pliegues anteriormente. natsEs un anamorfismo para natsBuilder.

Otro ejemplo, una función que toma un valor y una función y devuelve un flujo de aplicaciones sucesivas de la función al valor:

iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))

Su función constructora es la siguiente:

iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)

Entonces iteratees un anamorfismo para iterateBuilder.

Conclusión

En resumen, las álgebras F permiten definir pliegues, es decir, operaciones que reducen la estructura recursiva a un valor único, y las álgebras F permiten hacer lo contrario: construir una estructura [potencialmente] infinita a partir de un valor único.

De hecho, en Haskell F-álgebras y F-coalgebras coinciden. Esta es una propiedad muy agradable que es consecuencia de la presencia del valor 'inferior' en cada tipo. Por lo tanto, en Haskell se pueden crear pliegues y despliegues para cada tipo recursivo. Sin embargo, el modelo teórico detrás de esto es más complejo que el que he presentado anteriormente, por lo que deliberadamente lo he evitado.

Espero que esto ayude.


El tipo y la definición de se appendReductorve un poco extraño y realmente no me ayudó a ver el patrón allí ... :) ¿Puede verificar que sea correcto? ... ¿Cómo deberían ser los tipos de reductor en general? En la definición de rallí, ¿está F1determinado por IntList o es una F arbitraria?
Max Galkin

37

Repaso del documento tutorial Un tutorial sobre (co) álgebras y (co) inducción debería brindarle una idea sobre el co-álgebra en informática.

A continuación hay una cita para convencerte,

En términos generales, un programa en algún lenguaje de programación manipula datos. Durante el desarrollo de la informática en las últimas décadas, se hizo evidente que una descripción abstracta de estos datos es deseable, por ejemplo, para garantizar que el programa no dependa de la representación particular de los datos en los que opera. Además, tal abstracción facilita las pruebas de corrección.
Este deseo condujo al uso de métodos algebraicos en informática, en una rama llamada especificación algebraica o teoría del tipo de datos abstractos. El objeto de estudio son los tipos de datos en sí mismos, utilizando nociones de técnicas que son familiares para el álgebra. Los tipos de datos utilizados por los informáticos a menudo se generan a partir de una determinada colección de operaciones (constructoras), y es por esta razón que la "inicialidad" de las álgebras juega un papel tan importante.
Las técnicas algebraicas estándar han demostrado ser útiles para capturar varios aspectos esenciales de las estructuras de datos utilizadas en informática. Pero resultó ser difícil describir algebraicamente algunas de las estructuras dinámicas inherentes que ocurren en la informática. Dichas estructuras generalmente implican una noción de estado, que puede transformarse de varias maneras. Los enfoques formales de tales sistemas dinámicos basados ​​en el estado generalmente hacen uso de autómatas o sistemas de transición, como referencias tempranas clásicas.
Durante la última década, el conocimiento creció gradualmente de que tales sistemas basados ​​en el estado no deberían describirse como álgebras, sino como los llamados co-álgebras. Estos son los duales formales de las álgebras, de una manera que se precisará en este tutorial. La doble propiedad de "inicialidad" para las álgebras, es decir, la finalidad resultó ser crucial para tales co-álgebras. Y el principio de razonamiento lógico que se necesita para tales co-álgebras finales no es la inducción sino la co-inducción.


Preludio, sobre la teoría de la categoría. La teoría de categorías debe ser renombrar la teoría de los functores. Como categorías son lo que uno debe definir para definir functors. (Además, los functores son lo que uno debe definir para definir las transformaciones naturales).

¿Qué es un functor? Es una transformación de un conjunto a otro que preserva su estructura. (Para más detalles hay mucha buena descripción en la red).

¿Qué es un álgebra F? Es el álgebra del functor. Es solo el estudio de la propiedad universal del functor.

¿Cómo se puede vincular a la informática? El programa se puede ver como un conjunto estructurado de información. La ejecución del programa corresponde a la modificación de este conjunto estructurado de información. Suena bien que la ejecución debe preservar la estructura del programa. Luego, la ejecución puede verse como la aplicación de un functor sobre este conjunto de información. (El que define el programa).

¿Por qué F-co-álgebra? Los programas son duales por esencia, ya que se describen por información y actúan en consecuencia. Entonces, principalmente, la información que compone el programa y los modifica puede verse de dos maneras.

  • Datos que pueden definirse como la información que procesa el programa.
  • Indique qué se puede definir como la información que comparte el programa.

Entonces, en esta etapa, me gustaría decir que,

  • F-álgebra es el estudio de la transformación functoral que actúa sobre el universo de datos (como se ha definido aquí).
  • F-co-algebras es el estudio de la transformación functoral que actúa sobre el Universo del Estado (como se ha definido aquí).

Durante la vida de un programa, los datos y el estado coexisten, y se completan mutuamente. Son duales


5

Comenzaré con cosas que obviamente están relacionadas con la programación y luego agregaré algunas cosas de matemáticas, para mantenerlo lo más concreto y práctico posible.


Citemos a algunos informáticos sobre coinducción ...

http://www.cs.umd.edu/~micinski/posts/2012-09-04-on-understanding-coinduction.html

La inducción se trata de datos finitos, la co-inducción se trata de datos infinitos.

El ejemplo típico de datos infinitos es el tipo de una lista diferida (una secuencia). Por ejemplo, supongamos que tenemos el siguiente objeto en la memoria:

 let (pi : int list) = (* some function which computes the digits of
 π. *)

¡La computadora no puede contener todo π, porque solo tiene una cantidad finita de memoria! Pero lo que puede hacer es mantener un programa finito, que producirá cualquier expansión arbitrariamente larga de π que desee. Siempre que solo use piezas finitas de la lista, puede calcular con esa lista infinita todo lo que necesite.

Sin embargo, considere el siguiente programa:

let print_third_element (k : int list) =   match k with
     | _ :: _ :: thd :: tl -> print thd


 print_third_element pi

Este programa debería imprimir el tercer dígito de pi. Pero en algunos idiomas, cualquier argumento a una función se evalúa antes de pasar a una función (evaluación estricta, no perezosa). Si usamos este orden de reducción, nuestro programa anterior se ejecutará para siempre calculando los dígitos de pi antes de que pueda pasar a nuestra función de impresora (lo que nunca sucede) Como la máquina no tiene memoria infinita, el programa eventualmente se quedará sin memoria y se bloqueará. Este podría no ser el mejor orden de evaluación.

http://adam.chlipala.net/cpdt/html/Coinductive.html

En lenguajes de programación funcionales perezosos como Haskell, hay infinitas estructuras de datos en todas partes. Las listas infinitas y los tipos de datos más exóticos proporcionan abstracciones convenientes para la comunicación entre partes de un programa. Lograr una comodidad similar sin estructuras perezosas infinitas requeriría, en muchos casos, inversiones acrobáticas de flujo de control.

http://www.alexandrasilva.org/#/talks.html ejemplos de coalgebras por Alexandra Silva


Relacionar el contexto matemático ambiental con las tareas de programación habituales.

¿Qué es "un álgebra"?

Las estructuras algebraicas generalmente se ven así:

  1. Cosas
  2. ¿Qué pueden hacer las cosas?

Esto debería sonar como objetos con 1. propiedades y 2. métodos. O incluso mejor, debería sonar como firmas de tipo.

Los ejemplos matemáticos estándar incluyen monoide ⊃ grupo ⊃ vector-espacio ⊃ "un álgebra". Los monoides son como autómatas: secuencias de verbos (por ejemplo, f.g.h.h.nothing.f.g.f). Un gitregistro que siempre agrega el historial y nunca lo elimina sería un monoide pero no un grupo. Si agrega inversos (por ejemplo, números negativos, fracciones, raíces, eliminando el historial acumulado, deshaciendo un espejo roto), obtiene un grupo.

Los grupos contienen cosas que se pueden sumar o restar juntas. Por ejemplo, Durations se pueden agregar juntos. (Pero Dates no puede). Las duraciones viven en un espacio vectorial (no solo un grupo) porque también pueden ser escaladas por números externos. (Una firma tipo de scaling :: (Number,Duration) → Duration.)

Las álgebras ⊂ espacios vectoriales pueden hacer otra cosa: hay algunas m :: (T,T) → T. Llame a esto "multiplicación" o no, porque una vez que se va Integers, es menos obvio qué debería ser la "multiplicación" (o "exponenciación" ).

(Esta es la razón por la gente mira a () propiedades universales categoría-teórico: para decirles lo que la multiplicación debería hacer o ser como :

propiedad universal del producto )


Álgebras → Coalgebras

La multiplicación es más fácil de definir de una manera que se siente no arbitraria, que la multiplicación, porque para continuar T → (T,T), puede repetir el mismo elemento. ("mapa diagonal" - como matrices diagonales / operadores en teoría espectral)

Counit es por lo general la traza (suma de elementos de la diagonal), aunque de nuevo lo que es importante es lo que su counit hace ; tracees solo una buena respuesta para matrices.

La razón para mirar un espacio dual , en general, es porque es más fácil pensar en ese espacio. Por ejemplo, a veces es más fácil pensar en un vector normal que en el plano en el que es normal, pero puede controlar los planos (incluidos los hiperplanos) con vectores (y ahora estoy hablando del vector geométrico familiar, como en un trazador de rayos) .


Domar datos (no) estructurados

Los matemáticos podrían estar modelando algo divertido como TQFT , mientras que los programadores tienen que luchar con

  • fechas / horas ( + :: (Date,Duration) → Date),
  • lugares ( Paris(+48.8567,+2.3508)! Es una forma, no un punto),
  • JSON no estructurado que se supone que es consistente en algún sentido,
  • XML incorrecto pero cercano,
  • Datos SIG increíblemente complejos que deberían satisfacer un montón de relaciones sensatas,
  • expresiones regulares que significan algo para ti, pero significan mucho menos para perl.
  • CRM que debe contener todos los números de teléfono del ejecutivo y las ubicaciones de las villas, los nombres de su (ahora ex) esposa e hijos, cumpleaños y todos los obsequios anteriores, cada uno de los cuales debe satisfacer relaciones "obvias" (obvias para el cliente) que son increíblemente difícil de codificar,
  • .....

Los científicos de la computación, cuando hablan de carbongebras, generalmente tienen en mente operaciones fijas, como el producto cartesiano. Creo que esto es lo que la gente quiere decir cuando dice "Las álgebras son coalgebras en Haskell". Pero en la medida en que los programadores tienen que modelar tipos de datos complejos como Place, Date/Timey, y Customerhacer que esos modelos se parezcan tanto al mundo real (o al menos a la visión del mundo real del usuario final) como sea posible, creo que los duales, podría ser útil más allá de solo set-world.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.