Cosa significa "coalgebra" nel contesto della programmazione?


339

Ho sentito il termine "coalgebre" più volte nella programmazione funzionale e nei circoli PLT, specialmente quando la discussione riguarda oggetti, componenti, obiettivi e così via. Cercare su Google questo termine fornisce pagine che descrivono matematicamente queste strutture, il che è praticamente incomprensibile per me. Qualcuno può spiegare cosa significano le coalgebre nel contesto della programmazione, qual è il loro significato e in che modo si relazionano con oggetti e caratteristiche comuni?


21
Potrei raccomandare l'eccellente libro Patterns in FP di Jeremy Gibbons: patternsinfp.wordpress.com e il suo articolo abbastanza comprensibile "Calcolo dei programmi funzionali"? Entrambi coprono le coalgebre in modo abbastanza rigoroso (rispetto, ad esempio, a un post sul blog), ma sono anche abbastanza autosufficienti per qualcuno che conosce un po 'di Haskell.
Kristopher Micinski,

2
@KristopherMicinski, molto interessante. Grazie!
missingfaktor

Risposte:


474

Algebras

Penso che il punto di partenza sarebbe capire l'idea di un'algebra . Questa è solo una generalizzazione di strutture algebriche come gruppi, anelli, monoidi e così via. Il più delle volte, queste cose sono introdotte in termini di set, ma poiché siamo tra amici, parlerò invece dei tipi di Haskell. (Non posso resistere usando alcune lettere greche, però, rendono tutto più bello!)

Un'algebra, quindi, è solo un tipo τcon alcune funzioni e identità. Queste funzioni accettano numeri diversi di argomenti di tipo τe producono un τ: senza fretta, sembrano tutti simili (τ, τ,…, τ) → τ. Possono anche avere "identità", elementi τche hanno un comportamento speciale con alcune delle funzioni.

L'esempio più semplice di questo è il monoide. Un monoide è qualsiasi tipo τcon una funzione mappend ∷ (τ, τ) → τe un'identità mzero ∷ τ. Altri esempi includono cose come gruppi (che sono proprio come i monoidi tranne con una invert ∷ τ → τfunzione extra ), anelli, reticoli e così via.

Tutte le funzioni τsono attive ma possono avere diverse arità. Possiamo scriverli come τⁿ → τ, dove τⁿmappano una tupla di n τ. In questo modo, ha senso pensare alle identità come τ⁰ → τdove si τ⁰trova solo la tupla vuota (). Quindi ora possiamo semplificare l'idea di un'algebra: è solo un tipo con un certo numero di funzioni.

Un'algebra è solo un modello comune in matematica che è stato "preso in considerazione", proprio come facciamo con il codice. La gente ha notato che un sacco di cose interessanti - i suddetti monoidi, gruppi, reticoli e così via - seguono tutti uno schema simile, quindi lo hanno estratto. Il vantaggio di farlo è lo stesso della programmazione: crea prove riutilizzabili e facilita alcuni tipi di ragionamento.

F-algebre

Tuttavia, non abbiamo ancora finito con il factoring. Finora abbiamo un sacco di funzioni τⁿ → τ. Possiamo effettivamente fare un trucco per combinarli tutti in un'unica funzione. In particolare, diamo un'occhiata ai monoidi: abbiamo mappend ∷ (τ, τ) → τe mempty ∷ () → τ. Possiamo trasformarli in una singola funzione usando un tipo di somma— Either. Sarebbe così:

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

Possiamo effettivamente utilizzare questa trasformazione ripetutamente per combinare tutte le τⁿ → τfunzioni in una sola, per qualsiasi algebra. (In realtà, siamo in grado di fare questo per qualsiasi numero di funzioni a → τ, b → τe così via per qualsiasi a, b,… .)

Questo ci permette di parlare di algebre come un tipo τcon una singola funzione da qualche pasticcio di Eithers a un singolo τ. Per monoidi, questo casino è: Either (τ, τ) (); per i gruppi (che hanno un extra τ → τdi funzionamento), è: Either (Either (τ, τ) τ) (). È un tipo diverso per ogni diversa struttura. Quindi cosa hanno in comune tutti questi tipi? La cosa più ovvia è che sono solo somme di prodotti, tipi di dati algebrici. Ad esempio, per i monoidi, potremmo creare un tipo di argomento monoid che funzioni per qualsiasi monoid τ:

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

Possiamo fare la stessa cosa per gruppi, anelli e reticoli e tutte le altre possibili strutture.

Cos'altro ha di speciale tutti questi tipi? Bene, sono tutti Functors! Per esempio:

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

Quindi possiamo generalizzare ancora di più la nostra idea di algebra. È solo un tipo τcon una funzione f τ → τper qualche funzione f. In effetti, potremmo scriverlo come una typeclass:

class Functor f  Algebra f τ where
  op  f τ  τ

Questo è spesso chiamato "algebra F" perché è determinato dal funzione F. Se potessimo applicare parzialmente le macchine da scrivere, potremmo definire qualcosa di simile class Monoid = Algebra MonoidArgument.

coalgebre

Spero che abbiate una buona comprensione di cosa sia un'algebra e di come sia solo una generalizzazione delle normali strutture algebriche. Quindi cos'è una F-coalgebra? Bene, il co implica che è il "doppio" di un'algebra, cioè prendiamo un'algebra e lanciamo alcune frecce. Vedo solo una freccia nella definizione sopra, quindi capovolgerò che:

class Functor f  CoAlgebra f τ where
  coop  τ  f τ

E questo è tutto! Ora, questa conclusione può sembrare un po 'irriverente (eh). Ti dice cos'è una coalgebra, ma in realtà non fornisce alcuna idea di come sia utile o perché ci preoccupiamo. Ci arrivo tra un po ', una volta che trovo o trovo un buon esempio o due: P.

Classi e oggetti

Dopo aver letto un po ', penso di avere una buona idea di come usare le coalgebre per rappresentare classi e oggetti. Abbiamo un tipo Cche contiene tutti i possibili stati interni degli oggetti nella classe; la classe stessa è una coalgebra Cche specifica i metodi e le proprietà degli oggetti.

Come mostrato nell'esempio di algebra, se abbiamo un sacco di funzioni come a → τe b → τper qualsiasi a, b,…, possiamo combinarle tutte in un'unica funzione usando Eitherun tipo di somma. La doppia "nozione" consisterebbe nel combinare un mucchio di funzioni di tipo τ → a, τ → be così via. Possiamo farlo usando il doppio di un tipo di somma, un tipo di prodotto. Quindi, date le due funzioni sopra (chiamato fe g), possiamo crearne una singola in questo modo:

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

Il tipo (a, a)è un funzione nel modo più semplice, quindi sicuramente si adatta alla nostra nozione di una F-coalgebra. Questo particolare trucco ci consente di raggruppare un sacco di funzioni diverse, o, per OOP, metodi, in un'unica funzione di tipo τ → f τ.

Gli elementi del nostro tipo Crappresentano lo stato interno dell'oggetto. Se l'oggetto ha alcune proprietà leggibili, devono poter dipendere dallo stato. Il modo più ovvio per farlo è farne una funzione C. Quindi, se vogliamo una proprietà length (ad es. object.length), Avremmo una funzione C → Int.

Vogliamo metodi che possano accettare un argomento e modificare lo stato. Per fare questo, dobbiamo prendere tutti gli argomenti e produrre un nuovo C. Immaginiamo un setPositionmetodo che prende un xe ycoordinate: object.setPosition(1, 2). Si sarebbe simile a questa: C → ((Int, Int) → C).

Il modello importante qui è che i "metodi" e le "proprietà" dell'oggetto prendono l'oggetto stesso come primo argomento. Questo è proprio come il selfparametro in Python e come l'implicito thisdi molte altre lingue. Un coalgebra essenzialmente solo incapsula il comportamento di prendere un selfparametro: questo è ciò che la prima Ca C → F Cè.

Quindi mettiamo tutto insieme. Immaginiamo una classe con una positionproprietà, una nameproprietà e una setPositionfunzione:

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

Abbiamo bisogno di due parti per rappresentare questa classe. Innanzitutto, dobbiamo rappresentare lo stato interno dell'oggetto; in questo caso contiene solo due se Intuna String. (Questo è il nostro tipo C.) Quindi dobbiamo trovare la coalgebra che rappresenta la classe.

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

Abbiamo due proprietà da scrivere. Sono piuttosto banali:

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

name  C  String
name self = _name self

Ora dobbiamo solo essere in grado di aggiornare la posizione:

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

Questo è proprio come una classe Python con le sue selfvariabili esplicite . Ora che abbiamo un sacco di self →funzioni, dobbiamo combinarle in un'unica funzione per la coalgebra. Possiamo farlo con una semplice tupla:

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

Il tipo ((Int, Int), String, (Int, Int) → c)-per qualsiasi c -è un funtore, quindi coopha la forma che vogliamo: Functor f ⇒ C → f C.

Detto questo, Cinsieme alla coopforma di una coalgebra che specifica la classe che ho dato sopra. Puoi vedere come possiamo usare questa stessa tecnica per specificare un numero qualsiasi di metodi e proprietà per i nostri oggetti.

Questo ci consente di utilizzare il ragionamento coalgebrico per affrontare le lezioni. Ad esempio, possiamo introdurre la nozione di "omomorfismo della F-coalgebra" per rappresentare le trasformazioni tra le classi. Questo è un termine che suona spaventoso che significa solo una trasformazione tra coalgebre che preserva la struttura. Questo rende molto più facile pensare a mappare le classi su altre classi.

In breve, una coalgebra F rappresenta una classe avendo un gruppo di proprietà e metodi che dipendono tutti da un selfparametro contenente lo stato interno di ciascun oggetto.

Altre categorie

Finora abbiamo parlato di algebre e coalgebre di tipo Haskell. Un'algebra è solo un tipo τcon una funzione f τ → τe una coalgebra è solo un tipo τcon una funzione τ → f τ.

Tuttavia, nulla lega davvero queste idee a Haskell in . In effetti, sono generalmente introdotti in termini di insiemi e funzioni matematiche piuttosto che di tipi e funzioni di Haskell. In effetti, possiamo generalizzare questi concetti a qualsiasi categoria!

Possiamo definire un'algebra F per una categoria C. Innanzitutto, abbiamo bisogno di un funzione, F : C → Ccioè di un endofunctor . (Tutti Haskell Functors sono effettivamente endofunctors da Hask → Hask.) Quindi, un'algebra è solo un oggetto Ada Cun morfismo F A → A. Una coalgebra è la stessa tranne che con A → F A.

Cosa guadagniamo considerando altre categorie? Bene, possiamo usare le stesse idee in contesti diversi. Come monadi. In Haskell, una monade è un tipo M ∷ ★ → ★con tre operazioni:

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

La mapfunzione è solo una prova del fatto che Mè un Functor. Quindi possiamo dire che una monade è solo una funzione con due operazioni: returne join.

I portatori formano una categoria autonoma, con i morfismi tra loro chiamati cosiddette "trasformazioni naturali". Una trasformazione naturale è solo un modo per trasformare un funzione in un altro preservandone la struttura. Ecco un bell'articolo che aiuta a spiegare l'idea. Parla concat, che è solo joinper le liste.

Con i funzioni di Haskell, la composizione di due funzioni è una funzione stessa. In pseudocodice, potremmo scrivere questo:

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

Questo ci aiuta a pensare joincome una mappatura da f ∘ f → f. Il tipo di joinè ∀α. f (f α) → f α. Intuitivamente, possiamo vedere come una funzione valida per tutti i tipi αpuò essere pensata come una trasformazione di f.

returnè una trasformazione simile. Il suo tipo è ∀α. α → f α. Sembra diverso: il primo αnon è "in" un funzione! Fortunatamente, siamo in grado di risolvere il problema con l'aggiunta di un funtore identità lì: ∀α. Identity α → f α. Quindi returnè una trasformazione Identity → f.

Ora possiamo pensare a una monade come a una semplice algebra basata su alcuni funzioni fcon operazioni f ∘ f → fe Identity → f. Non ti sembra familiare? È molto simile a un monoide, che era solo un tipo τcon operazioni τ × τ → τe () → τ.

Quindi una monade è proprio come un monoide, tranne per il fatto che invece di avere un tipo abbiamo un funzione. È lo stesso tipo di algebra, solo in una categoria diversa. (È qui che la frase "Una monade è solo un monoide nella categoria degli endofunctor" proviene da quanto ne so.)

Ora, abbiamo queste due operazioni: f ∘ f → fe Identity → f. Per ottenere la coalgebra corrispondente, lanciamo semplicemente le frecce. Questo ci dà due nuove operazioni: f → f ∘ fe f → Identity. Possiamo trasformarli in tipi di Haskell aggiungendo variabili di tipo come sopra, dandoci ∀α. f α → f (f α)e ∀α. f α → α. Questa sembra proprio la definizione di un comonad:

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

Quindi una comonade è quindi una coalgebra in una categoria di endofunctor.


45
Questo è incredibilmente prezioso. Sono riuscito a confondere alcune intuizioni su tutta questa faccenda dell'algebra F dalla lettura e dagli esempi (ad esempio, dal vedere il loro uso con i catamoprismi), ma questo è tutto perfettamente chiaro anche per me. Grazie!
Luis Casillas,

28
Questa è un'ottima spiegazione
Edward KMETT,

5
@EdwardKmett: grazie. Le cose che ho aggiunto su classi e oggetti vanno bene? Ne ho letto solo oggi, ma sembra avere senso.
Tikhon Jelvis,

7
Per quello che vale: la "categoria di endofunctor" qui è più precisamente una categoria i cui oggetti sono endofunctor su una categoria e le cui frecce sono trasformazioni naturali. Questa è una categoria monoideale, con composizione di funzione corrispondente (,)e funzione di identità a (). Un oggetto monoide all'interno di una categoria monoidale è un oggetto con frecce corrispondenti alla tua algebra monoide, che descrive un oggetto monoide in Hask con tipi di prodotto come struttura monoidale. Un oggetto monoide nella categoria di endofunctor su C è una monade su C, quindi sì, la tua comprensione è corretta. :]
CA McCann

8
È stato un finale epico!
jdinunzio,

85

Le algebre F e le coalgebre F sono strutture matematiche che sono strumentali nel ragionamento sui tipi induttivi (o tipi ricorsivi ).

F-algebre

Inizieremo prima con le F-algebre. Cercherò di essere il più semplice possibile.

Immagino che tu sappia cos'è un tipo ricorsivo. Ad esempio, questo è un tipo per un elenco di numeri interi:

data IntList = Nil | Cons (Int, IntList)

È ovvio che è ricorsivo, anzi, la sua definizione si riferisce a se stessa. La sua definizione è composta da due costruttori di dati, che hanno i seguenti tipi:

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

Nota che ho scritto il tipo di Nilas () -> IntList, non semplicemente IntList. Questi sono in realtà tipi equivalenti dal punto di vista teorico, perché il ()tipo ha un solo abitante.

Se scriviamo le firme di queste funzioni in un modo più teorico, otterremo

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

dove 1è un insieme di unità (impostato con un elemento) e l' A × Boperazione è un prodotto incrociato di due insiemi Ae B(ovvero, un insieme di coppie in (a, b)cui aattraversa tutti gli elementi Ae battraversa tutti gli elementi di B).

Unione disgiunta di due insiemi Aed Bè un insieme A | Bche è un'unione di insiemi {(a, 1) : a in A}e {(b, 2) : b in B}. Essenzialmente è un insieme di tutti gli elementi di entrambi Ae B, ma con ciascuno di questi elementi 'marcati' come appartenenti a uno Ao B, quindi quando scegliamo qualsiasi elemento da A | Bsapremo immediatamente se questo elemento proviene da Ao da B.

Possiamo "unire" Nile Consfunzioni, quindi formeranno un'unica funzione che lavora su un set 1 | (Int × IntList):

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

Infatti, se la Nil|Consfunzione viene applicata al ()valore (che, ovviamente, appartiene al 1 | (Int × IntList)set), allora si comporta come se fosse Nil; se Nil|Consviene applicato a qualsiasi valore di tipo (Int, IntList)(tali valori sono anche nell'insieme 1 | (Int × IntList), si comporta come Cons.

Ora considera un altro tipo di dati:

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

Ha i seguenti costruttori:

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

che può anche essere unito in una funzione:

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

Si può vedere che entrambe queste joinedfunzioni hanno un tipo simile: sembrano entrambe

f :: F T -> T

dove Fc'è un tipo di trasformazione che prende il nostro tipo e dà un tipo più complesso, che consiste di xe |operazioni, usi Te possibilmente altri tipi. Ad esempio, per IntListe si IntTree Fpresenta come segue:

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

Possiamo immediatamente notare che qualsiasi tipo algebrico può essere scritto in questo modo. In effetti, è per questo che sono chiamati "algebrici": consistono in una serie di "somme" (sindacati) e "prodotti" (prodotti incrociati) di altri tipi.

Ora possiamo definire l'algebra F. L'algebra F è solo una coppia (T, f), in cui Tesiste un tipo ed fè una funzione di tipo f :: F T -> T. Nei nostri esempi F-algebre sono (IntList, Nil|Cons)e (IntTree, Leaf|Branch). Si noti, tuttavia, che nonostante quel tipo di ffunzione è lo stesso per ogni F, Te fpossono essere arbitrari. Ad esempio, (String, g :: 1 | (Int x String) -> String)o (Double, h :: Int | (Double, Double) -> Double)per alcuni ge hsono anche F-algebre per la corrispondente F.

Successivamente possiamo introdurre omomorfismi dell'algebra F e quindi algebre F iniziali , che hanno proprietà molto utili. In effetti, (IntList, Nil|Cons)è un'algebra F1 iniziale ed (IntTree, Leaf|Branch)è un'algebra F2 iniziale. Non presenterò definizioni precise di questi termini e proprietà poiché sono più complessi e astratti del necessario.

Tuttavia, il fatto che, diciamo, (IntList, Nil|Cons)sia l'algebra F ci consente di definire una foldfunzione simile a questo tipo. Come sapete, fold è un tipo di operazione che trasforma alcuni tipi di dati ricorsivi in ​​un valore finito. Ad esempio, possiamo piegare un elenco di numeri interi in un singolo valore che è la somma di tutti gli elementi nell'elenco:

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

È possibile generalizzare tale operazione su qualsiasi tipo di dati ricorsivo.

Quella che segue è una firma della foldrfunzione:

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

Nota che ho usato delle parentesi graffe per separare i primi due argomenti dall'ultimo. Questa non è una vera foldrfunzione, ma è isomorfa (cioè puoi facilmente ottenerne una dall'altra e viceversa). Applicato parzialmente foldravrà la seguente firma:

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

Possiamo vedere che questa è una funzione che accetta un elenco di numeri interi e restituisce un singolo numero intero. Definiamo tale funzione in termini del nostro IntListtipo.

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

Vediamo che questa funzione è composta da due parti: la prima parte definisce il comportamento di questa funzione su Nilparte IntListe la seconda parte definisce il comportamento della funzione su Consparte.

Supponiamo ora che stiamo programmando non in Haskell ma in qualche linguaggio che consenta l'uso di tipi algebrici direttamente nelle firme dei tipi (beh, tecnicamente Haskell consente l'uso di tipi algebrici tramite tuple e Either a btipo di dati, ma questo porterà a verbosità inutili). Considera una funzione:

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

Si può vedere che reductorè una funzione di tipo F1 Int -> Int, proprio come nella definizione di F-algebra! In effetti, la coppia (Int, reductor)è una algebra di F1.

Perché IntListè un'algebra F1 iniziale, per ogni tipo Te per ogni funzione r :: F1 T -> Tesiste una funzione, chiamata catamorfismo per r, che si converte IntListin T, e tale funzione è unica. In effetti, nel nostro esempio un catamorfismo reductorè sumFold. Nota come reductore sumFoldsono simili: hanno quasi la stessa struttura! In reductordefinizione sUtilizzo parametro (tipo del quale corrisponde a T) corrisponde all'uso del risultato di calcolo di sumFold xsin sumFolddefinizione.

Giusto per renderlo più chiaro e aiutarti a vedere lo schema, ecco un altro esempio e ricominciamo dalla risultante funzione di piegatura. Considera la appendfunzione che aggiunge il suo primo argomento al secondo:

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

Ecco come appare sul nostro IntList:

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

Ancora una volta, proviamo a scrivere il reduttore:

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

appendFoldè un catamorphism per appendReductorche trasforma IntListin IntList.

Quindi, essenzialmente, le algebre F ci consentono di definire "pieghe" su strutture di dati ricorsive, cioè operazioni che riducono le nostre strutture a un certo valore.

F-coalgebre

Le F-coalgebre sono il cosiddetto termine "doppio" per le F-algebre. Ci consentono di definire unfoldstipi di dati ricorsivi, ovvero un modo per costruire strutture ricorsive da un certo valore.

Supponiamo di avere il seguente tipo:

data IntStream = Cons (Int, IntStream)

Questo è un flusso infinito di numeri interi. Il suo unico costruttore ha il seguente tipo:

Cons :: (Int, IntStream) -> IntStream

O, in termini di set

Cons :: Int × IntStream -> IntStream

Haskell ti consente di modellare la corrispondenza sui costruttori di dati, in modo da poter definire le seguenti funzioni lavorando su IntStreams:

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

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

Puoi naturalmente "unire" queste funzioni in un'unica funzione di tipo IntStream -> Int × IntStream:

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

Notare come il risultato della funzione coincida con la rappresentazione algebrica del nostro IntStreamtipo. Una cosa simile può essere fatta anche per altri tipi di dati ricorsivi. Forse hai già notato lo schema. Mi riferisco a una famiglia di funzioni di tipo

g :: T -> F T

dov'è un Tcerto tipo. D'ora in poi definiremo

F1 T = Int × T

Ora, F-coalgebra è una coppia (T, g), dove Tè un tipo ed gè una funzione del tipo g :: T -> F T. Ad esempio, (IntStream, head&tail)è una coalgebra F1. Ancora una volta, proprio come nelle algebre F, ge Tpuò essere arbitrario, per esempio, (String, h :: String -> Int x String)è anche una coalgebra F1 per qualche ora.

Tra tutte le coalgebre F ci sono le cosiddette coalgebre F terminali , che sono doppie rispetto alle algebre F iniziali. Ad esempio, IntStreamè una F-coalgebra terminale. Ciò significa che per ogni tipo Te per ogni funzione p :: T -> F1 Tesiste una funzione, chiamata anamorfismo , che si converte Tin IntStream, e tale funzione è unica.

Considera la seguente funzione, che genera un flusso di numeri interi successivi a partire da quello indicato:

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

Ora esaminiamo una funzione natsBuilder :: Int -> F1 Int, ovvero natsBuilder :: Int -> Int × Int:

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

Ancora una volta, possiamo vedere alcune somiglianze tra natse natsBuilder. È molto simile alla connessione che abbiamo osservato in precedenza con riduttori e pieghe. natsè un anamorfismo per natsBuilder.

Un altro esempio, una funzione che accetta un valore e una funzione e restituisce al flusso un flusso di applicazioni successive della funzione:

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

La sua funzione builder è la seguente:

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

Quindi iterateè un anamorfismo per iterateBuilder.

Conclusione

Quindi, in breve, le algebre F consentono di definire le pieghe, ovvero le operazioni che riducono la struttura ricorsiva in un singolo valore, e le coalgebre F consentono di fare il contrario: costruire una struttura [potenzialmente] infinita da un singolo valore.

In Haskell F-algebre e F-coalgebre coincidono infatti. Questa è una proprietà molto bella che è una conseguenza della presenza del valore 'bottom' in ogni tipo. Quindi in Haskell è possibile creare sia le pieghe che le spiegazioni per ogni tipo ricorsivo. Tuttavia, il modello teorico dietro questo è più complesso di quello che ho presentato sopra, quindi l'ho deliberatamente evitato.

Spero che questo ti aiuti.


Il tipo e la definizione di appendReductorsembra un po 'strano e non mi ha davvero aiutato a vedere lo schema lì ... :) Puoi ricontrollare che sia corretto? .. Come dovrebbero apparire i tipi di riduttore in generale? Nella definizione di rlì, è F1determinato dall'IntList o è una F arbitraria?
Max Galkin,

37

Esaminare il documento di esercitazione Un tutorial su (co) algebre e (co) induzione dovrebbe fornire alcune informazioni sulla co-algebra nell'informatica.

Di seguito è una citazione per convincerti,

In termini generali, un programma in alcuni linguaggi di programmazione manipola i dati. Durante lo sviluppo dell'informatica negli ultimi decenni è diventato chiaro che è auspicabile una descrizione astratta di questi dati, ad esempio per garantire che il proprio programma non dipenda dalla particolare rappresentazione dei dati su cui opera. Inoltre, tale astrattezza facilita le prove di correttezza.
Questo desiderio ha portato all'uso di metodi algebrici nell'informatica, in un ramo chiamato specifica algebrica o teoria dei tipi di dati astratti. L'oggetto dello studio sono i tipi di dati in sé, usando nozioni di tecniche che sono familiari dall'algebra. I tipi di dati utilizzati dagli informatici sono spesso generati da una determinata raccolta di operazioni (di costruzione), ed è per questo motivo che l '"inizializzazione" delle algebre gioca un ruolo così importante.
Le tecniche algebriche standard si sono dimostrate utili per acquisire vari aspetti essenziali delle strutture di dati utilizzate nell'informatica. Ma si è rivelato difficile descrivere algebricamente alcune delle strutture intrinsecamente dinamiche che si verificano nell'informatica. Tali strutture di solito implicano una nozione di stato, che può essere trasformata in vari modi. Gli approcci formali a tali sistemi dinamici basati sullo stato fanno generalmente uso di automi o sistemi di transizione, come riferimenti precoci classici.
Durante l'ultimo decennio è cresciuta gradualmente la comprensione che tali sistemi basati sullo stato non devono essere descritti come algebre, ma come cosiddette co-algebre. Questi sono i doppi formali delle algebre, in un modo che sarà reso preciso in questo tutorial. La duplice proprietà dell '"inizialità" per le algebre, vale a dire la finalità, si è rivelata cruciale per tali co-algebre. E il principio di ragionamento logico necessario per tali co-algebre finali non è l'induzione ma la coinduzione.


Preludio, sulla teoria delle categorie. La teoria delle categorie dovrebbe essere la ridenominazione della teoria dei funzioni. Poiché le categorie sono ciò che si deve definire per definire i funzione. (Inoltre, i funzioni sono ciò che si deve definire per definire le trasformazioni naturali.)

Che cos'è un funzione? È una trasformazione da un set all'altro che ne preserva la struttura. (Per maggiori dettagli c'è una buona descrizione in rete).

Che cos'è un'algebra F? È l'algebra di functor. È solo lo studio della proprietà universale del funzione.

Come può essere link all'informatica? Il programma può essere visualizzato come un insieme strutturato di informazioni. L'esecuzione del programma corrisponde alla modifica di questo insieme strutturato di informazioni. Sembra buono che l'esecuzione dovrebbe preservare la struttura del programma. Quindi l'esecuzione può essere vista come l'applicazione di un funzione su questo insieme di informazioni. (Quello che definisce il programma).

Perché la co-algebra? Il programma è duplice per essenza in quanto sono descritti da informazioni e agiscono su di esso. Quindi, principalmente le informazioni che compongono il programma e le fanno cambiare possono essere visualizzate in due modi.

  • Dati che possono essere definiti come informazioni elaborate dal programma.
  • Stato che può essere definito come l'informazione condivisa dal programma.

Quindi a questo punto, vorrei dire che,

  • L'algebra F è lo studio della trasformazione funzionale che agisce sull'universo di Data (come definito qui).
  • F-co-algebre è lo studio della trasformazione funzionale che agisce sull'universo di stato (come definito qui).

Durante la vita di un programma, i dati e lo stato coesistono e si completano a vicenda. Sono doppi.


5

Inizierò con cose che sono ovviamente legate alla programmazione e poi aggiungerò alcune cose matematiche, per mantenerle il più concrete e concrete possibile.


Citiamo alcuni informatici sulla coinduzione ...

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

L'induzione riguarda dati finiti, la coinduzione riguarda dati infiniti.

L'esempio tipico di infiniti dati è il tipo di un elenco pigro (un flusso). Ad esempio, supponiamo che abbiamo in memoria il seguente oggetto:

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

Il computer non può contenere tutto π, perché ha solo una quantità limitata di memoria! Ma quello che può fare è tenere un programma finito, che produrrà qualsiasi espansione arbitrariamente lunga di π che desideri. Fintanto che usi solo pezzi finiti dell'elenco, puoi calcolare con quell'infinito elenco tutto il necessario.

Tuttavia, considera il seguente programma:

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


 print_third_element pi

Questo programma dovrebbe stampare la terza cifra di pi. Ma in alcune lingue, qualsiasi argomento di una funzione viene valutato prima di essere passato in una funzione (valutazione rigorosa, non pigra). Se utilizziamo questo ordine di riduzione, il nostro programma sopra funzionerà per sempre calcolando le cifre di pi prima che possa essere passato alla nostra funzione di stampa (cosa che non accade mai). Poiché la macchina non ha memoria infinita, il programma finirà per esaurire la memoria e si arresterà in modo anomalo. Questo potrebbe non essere il miglior ordine di valutazione.

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

In linguaggi di programmazione funzionale pigri come Haskell, infinite strutture di dati sono ovunque. Elenchi infiniti e tipi di dati più esotici forniscono astrazioni convenienti per la comunicazione tra le parti di un programma. Raggiungere una convenienza simile senza infinite strutture pigre richiederebbe, in molti casi, inversioni acrobatiche del flusso di controllo.

http://www.alexandrasilva.org/#/talks.html esempi di coalgebre di Alexandra Silva


Correlare il contesto matematico ambientale con le normali attività di programmazione

Che cos'è "un'algebra"?

Le strutture algebriche generalmente sembrano:

  1. Cose
  2. Cosa può fare la roba

Questo dovrebbe suonare come oggetti con 1. proprietà e 2. metodi. O ancora meglio, dovrebbe sembrare un tipo di firma.

Esempi matematici standard includono monoide ⊃ gruppo ⊃ spazio vettoriale an "algebra". I monoidi sono come gli automi: sequenze di verbi (ad esempio, f.g.h.h.nothing.f.g.f). Un gitregistro che aggiunge sempre la cronologia e non elimina mai sarebbe un monoide ma non un gruppo. Se si aggiungono inversioni (ad es. Numeri negativi, frazioni, radici, eliminazione della storia accumulata, distruzione di uno specchio rotto) si ottiene un gruppo.

I gruppi contengono elementi che possono essere aggiunti o sottratti insieme. Ad esempio, Durationè possibile aggiungere s insieme. (Ma Datenon è possibile.) Le durate vivono in uno spazio vettoriale (non solo un gruppo) perché possono anche essere ridimensionate da numeri esterni. (Una firma di tipo di scaling :: (Number,Duration) → Duration.)

Algebre spaces gli spazi vettoriali possono fare ancora un'altra cosa: ce ne sono alcuni m :: (T,T) → T. Chiamare questa "moltiplicazione" o no, perché una volta usciti Integersè meno ovvio quale dovrebbe essere la "moltiplicazione" (o "esponenziazione" ).

(Questo è il motivo per cui la gente guarda a () proprietà universali categoria-teorica: per dire loro ciò che la moltiplicazione dovrebbe fare o essere come :

proprietà universale del prodotto )


Algebre → Coalgebre

La moltiplicazione è più facile da definire in un modo che non sembra arbitrario, piuttosto che la moltiplicazione, perché per passare da T → (T,T)te puoi semplicemente ripetere lo stesso elemento. ("mappa diagonale" - come matrici / operatori diagonali nella teoria spettrale)

Counit è di solito la traccia (somma di elementi diagonali), anche se ancora una volta ciò che è importante è ciò che il vostro counit fa ; traceè solo una buona risposta per le matrici.

Il motivo per guardare a uno spazio doppio , in generale, è perché è più facile pensare in quello spazio. Ad esempio, a volte è più facile pensare a un vettore normale piuttosto che al piano a cui è normale, ma puoi controllare i piani (compresi gli iperpiani) con i vettori (e ora sto parlando del familiare vettore geometrico, come in un ray-tracer) .


Domare dati (non) strutturati

I matematici potrebbero modellare qualcosa di divertente come TQFT , mentre i programmatori devono combattere

  • date / orari ( + :: (Date,Duration) → Date),
  • luoghi ( Paris(+48.8567,+2.3508)! È una forma, non un punto.),
  • JSON non strutturato che dovrebbe essere coerente in un certo senso,
  • XML errato ma vicino,
  • dati GIS incredibilmente complessi che dovrebbero soddisfare un sacco di relazioni sensibili,
  • espressioni regolari che significano qualcosa per te, ma significano molto meno per perl.
  • CRM che dovrebbe contenere tutti i numeri di telefono del dirigente e le posizioni della villa, i nomi della sua (ora ex-) moglie e figli, il compleanno e tutti i regali precedenti, ognuno dei quali dovrebbe soddisfare relazioni "ovvie" (ovvie per il cliente) che sono incredibilmente difficile da codificare,
  • .....

Gli informatici, quando si parla di coalgebre, di solito hanno in mente operazioni definite, come il prodotto cartesiano. Credo che questo sia ciò che la gente intende quando dicono "Algebre sono coalgebre in Haskell". Ma nella misura in cui i programmatori devono modellare tipi di dati complessi come Place, Date/Timee Customer- e far sì che quei modelli assomiglino il più possibile al mondo reale (o almeno alla visione del mondo reale dell'utente finale) - credo che i dualisti, potrebbe essere utile al di là del solo mondo set.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.