Que signifie «coalgebra» dans le contexte de la programmation?

339

J'ai entendu le terme "coalgebras" à plusieurs reprises dans la programmation fonctionnelle et les cercles PLT, en particulier lorsque la discussion porte sur des objets, des comonades, des lentilles, etc. Googler ce terme donne des pages qui donnent une description mathématique de ces structures qui est à peu près incompréhensible pour moi. Quelqu'un peut-il expliquer ce que les coalgebras signifient dans le contexte de la programmation, quelle est leur signification et comment ils se rapportent aux objets et aux comonades?

missingfaktor
la source
21
Puis-je recommander l'excellent livre Patterns in FP de Jeremy Gibbons: patternsinfp.wordpress.com et son article très compréhensible "Calculating Functional Programs"? Ils couvrent tous deux les coalgebras d'une manière assez rigoureuse (par exemple, par exemple, un article de blog), mais ils sont également assez autonomes pour quelqu'un qui connaît un peu Haskell.
Kristopher Micinski
2
@KristopherMicinski, très intéressant. Merci!
missingfaktor

Réponses:

474

Algèbres

Je pense que le point de départ serait de comprendre l'idée d'une algèbre . Ce n'est qu'une généralisation des structures algébriques comme les groupes, les anneaux, les monoides et ainsi de suite. La plupart du temps, ces choses sont introduites en termes d'ensembles, mais comme nous sommes entre amis, je parlerai plutôt des types Haskell. (Je ne peux pas résister à utiliser des lettres grecques, elles rendent tout plus cool!)

Une algèbre n'est donc qu'un type τavec quelques fonctions et identités. Ces fonctions prennent des nombres différents d'arguments de type τet produisent un τ: non-curry, ils ressemblent tous (τ, τ,…, τ) → τ. Ils peuvent également avoir des «identités» - des éléments τqui ont un comportement spécial avec certaines des fonctions.

L'exemple le plus simple est le monoïde. Un monoïde est tout type τayant une fonction mappend ∷ (τ, τ) → τet une identité mzero ∷ τ. D'autres exemples incluent des choses comme des groupes (qui sont comme des monoïdes sauf avec une invert ∷ τ → τfonction supplémentaire ), des anneaux, des réseaux et ainsi de suite.

Toutes les fonctions fonctionnent τmais peuvent avoir des arités différentes. Nous pouvons les écrire comme τⁿ → τ, où τⁿcorrespond à un tuple de n τ. De cette façon, il est logique de penser aux identités comme τ⁰ → τoù se τ⁰trouve juste le tuple vide (). Nous pouvons donc simplifier l'idée d'une algèbre maintenant: c'est juste un type avec un certain nombre de fonctions.

Une algèbre n'est qu'un modèle courant en mathématiques qui a été «factorisé», tout comme nous le faisons avec le code. Les gens ont remarqué que tout un tas de choses intéressantes - les monoïdes, les groupes, les réseaux et ainsi de suite susmentionnés - suivent tous un modèle similaire, alors ils l'ont résumé. L'avantage de faire cela est le même qu'en programmation: il crée des preuves réutilisables et facilite certains types de raisonnement.

Algèbres F

Cependant, nous n'avons pas tout à fait fini avec l'affacturage. Jusqu'à présent, nous avons un tas de fonctions τⁿ → τ. Nous pouvons réellement faire une astuce pour les combiner tous en une seule fonction. En particulier, regardons les monoïdes: nous avons mappend ∷ (τ, τ) → τet mempty ∷ () → τ. Nous pouvons les transformer en une seule fonction en utilisant un type de somme— Either. Cela ressemblerait à ceci:

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

Nous pouvons réellement utiliser cette transformation à plusieurs reprises pour combiner toutes les τⁿ → τfonctions en une seule, pour n'importe quelle algèbre. (En fait, nous pouvons le faire pour n'importe quel nombre de fonctions a → τ, b → τet ainsi de suite pour toutes a, b,… .)

Cela nous permet de parler des algèbres en tant que type τavec une seule fonction d'un certain désordre de Eithers à un seul τ. Pour monoids, ce gâchis est: Either (τ, τ) (); pour les groupes (qui ont un supplément de τ → τfonctionnement), il est: Either (Either (τ, τ) τ) (). C'est un type différent pour chaque structure différente. Alors, qu'est-ce que tous ces types ont en commun? La chose la plus évidente est que ce ne sont que des sommes de produits - des types de données algébriques. Par exemple, pour les monoïdes, nous pourrions créer un type d'argument monoïde qui fonctionne pour tout monoïde τ:

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

Nous pouvons faire la même chose pour les groupes et les anneaux et les réseaux et toutes les autres structures possibles.

Quoi d'autre est spécial sur tous ces types? Eh bien, ils sont tous Functors! Par exemple:

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

Nous pouvons donc généraliser encore plus notre idée d'une algèbre. C'est juste un type τavec une fonction f τ → τpour un foncteur f. En fait, nous pourrions l'écrire sous forme de classe:

class Functor f  Algebra f τ where
  op  f τ  τ

Ceci est souvent appelé «algèbre F» car il est déterminé par le foncteur F. Si nous pouvions appliquer partiellement des classes de caractères, nous pourrions définir quelque chose comme class Monoid = Algebra MonoidArgument.

Coalgebras

Maintenant, j'espère que vous avez une bonne compréhension de ce qu'est une algèbre et comment c'est juste une généralisation des structures algébriques normales. Alors qu'est-ce qu'un F-coalgebra? Eh bien, le co implique que c'est le "double" d'une algèbre, c'est-à-dire que nous prenons une algèbre et retournons quelques flèches. Je ne vois qu'une seule flèche dans la définition ci-dessus, donc je vais juste inverser cela:

class Functor f  CoAlgebra f τ where
  coop  τ  f τ

Et c'est tout! Maintenant, cette conclusion peut sembler un peu désinvolte (hé). Il vous dit ce qu'est un charbon, mais ne donne pas vraiment un aperçu de son utilité ou pourquoi nous nous en soucions. J'y reviendrai dans un instant, une fois que je trouverai ou trouverai un bon exemple ou deux: P.

Classes et objets

Après avoir lu un peu, je pense avoir une bonne idée de la façon d'utiliser les coalgebras pour représenter les classes et les objets. Nous avons un type Cqui contient tous les états internes possibles des objets de la classe; la classe elle-même est une houille Cqui spécifie les méthodes et les propriétés des objets.

Comme le montre l'exemple d'algèbre, si nous avons un tas de fonctions comme a → τet b → τpour n'importe laquelle a, b,…, nous pouvons les combiner toutes en une seule fonction en utilisant Either, un type de somme. La double «notion» consisterait à combiner un tas de fonctions de type τ → a, τ → bet ainsi de suite. Nous pouvons le faire en utilisant le double d'un type de somme - un type de produit. Donc, étant donné les deux fonctions ci-dessus (appelées fet g), nous pouvons en créer une seule comme ceci:

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

Le type (a, a)est un foncteur au sens simple du terme, il correspond donc certainement à notre notion de F-coalgebra. Cette astuce particulière nous permet de regrouper un tas de fonctions différentes - ou, pour la POO, les méthodes - en une seule fonction de type τ → f τ.

Les éléments de notre type Creprésentent l' état interne de l'objet. Si l'objet a des propriétés lisibles, elles doivent pouvoir dépendre de l'état. La façon la plus évidente de le faire est d'en faire une fonction C. Donc, si nous voulons une propriété de longueur (par exemple object.length), nous aurions une fonction C → Int.

Nous voulons des méthodes qui peuvent prendre un argument et modifier l'état. Pour ce faire, nous devons prendre tous les arguments et en produire un nouveau C. Imaginons une setPositionméthode qui prend une xet ycoordonnée: object.setPosition(1, 2). Il ressemblerait à ceci: C → ((Int, Int) → C).

Le motif important ici est que les "méthodes" et les "propriétés" de l'objet prennent l'objet lui-même comme premier argument. C'est exactement comme le selfparamètre en Python et comme l'implicite thisde nombreux autres langages. Un cogèbre essentiellement encapsule tout le comportement de prendre un selfparamètre: c'est ce que le premier Cen C → F Cest.

Alors mettons tout cela ensemble. Imaginons une classe avec une positionpropriété, une namepropriété et une setPositionfonction:

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

Nous avons besoin de deux parties pour représenter cette classe. Premièrement, nous devons représenter l'état interne de l'objet; dans ce cas, il contient juste deux Ints et un String. (C'est notre type C.) Ensuite, nous devons trouver le charbon de bois représentant la classe.

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

Nous avons deux propriétés à écrire. Ils sont assez banals:

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

name  C  String
name self = _name self

Il nous suffit maintenant de pouvoir mettre à jour la position:

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

C'est comme une classe Python avec ses selfvariables explicites . Maintenant que nous avons un tas de self →fonctions, nous devons les combiner en une seule fonction pour la houille. Nous pouvons le faire avec un simple tuple:

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

Le type ((Int, Int), String, (Int, Int) → c)-pour tout c -est un foncteur, donc coopa le forme que nous voulons: Functor f ⇒ C → f C.

Compte tenu de cela, Cainsi que de coopformer une houillère qui spécifie la classe que j'ai donnée ci-dessus. Vous pouvez voir comment nous pouvons utiliser cette même technique pour spécifier un nombre illimité de méthodes et de propriétés pour nos objets.

Cela nous permet d'utiliser le raisonnement coalgebrique pour traiter les classes. Par exemple, nous pouvons introduire la notion d '"homomorphisme de F-coalgebra" pour représenter les transformations entre classes. C'est un terme qui fait peur et qui signifie simplement une transformation entre les houillères qui préserve la structure. Cela facilite beaucoup la réflexion sur le mappage de classes sur d'autres classes.

En bref, un F-coalgebra représente une classe en ayant un tas de propriétés et de méthodes qui dépendent toutes d'un selfparamètre contenant l'état interne de chaque objet.

Autres catégories

Jusqu'à présent, nous avons parlé des algèbres et des houillères en tant que types Haskell. Une algèbre n'est qu'un type τavec une fonction f τ → τet une houille est juste un type τavec une fonction τ → f τ.

Cependant, rien ne lie vraiment ces idées à Haskell en soi . En fait, ils sont généralement introduits en termes d'ensembles et de fonctions mathématiques plutôt que de types et de fonctions Haskell. En effet, nous pouvons généraliser ces concepts à toutes les catégories!

Nous pouvons définir une algèbre F pour une certaine catégorie C. Premièrement, nous avons besoin d'un foncteur, F : C → Cc'est-à-dire d'un endofoncteur . (Tous les Haskell Functorsont en fait des endofoncteurs de Hask → Hask.) Ensuite, une algèbre n'est qu'un objet Ade Cmorphisme F A → A. Une houille est la même sauf avec A → F A.

Qu'avons-nous à gagner en considérant d'autres catégories? Eh bien, nous pouvons utiliser les mêmes idées dans différents contextes. Comme des monades. À Haskell, une monade est un type M ∷ ★ → ★avec trois opérations:

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

La mapfonction n'est qu'une preuve du fait qu'il Ms'agit d'un Functor. On peut donc dire qu'une monade n'est qu'un foncteur avec deux opérations: returnet join.

Les foncteurs forment eux-mêmes une catégorie, les morphismes entre eux étant appelés «transformations naturelles». Une transformation naturelle n'est qu'un moyen de transformer un foncteur en un autre tout en préservant sa structure. Voici un bel article aidant à expliquer l'idée. Il en parle concat, qui ne joinconcerne que les listes.

Avec les foncteurs Haskell, la composition de deux foncteurs est un foncteur lui-même. En pseudocode, nous pourrions écrire ceci:

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

Cela nous aide à penser joincomme un mappage à partir de f ∘ f → f. Le type de joinest ∀α. f (f α) → f α. Intuitivement, nous pouvons voir comment une fonction valable pour tous les types αpeut être considérée comme une transformation de f.

returnest une transformation similaire. Son type est ∀α. α → f α. Cela semble différent - le premier αn'est pas "dans" un foncteur! Heureusement, nous pouvons résoudre ce problème en ajoutant un foncteur là d'identité: ∀α. Identity α → f α. Il en returnva de même pour une transformation Identity → f.

Maintenant, nous pouvons considérer une monade comme une simple algèbre basée sur un foncteur favec des opérations f ∘ f → fet Identity → f. Cela ne vous semble-t-il pas familier? Il est très similaire à un monoïde, qui n'était qu'un type τavec des opérations τ × τ → τet () → τ.

Une monade est donc comme un monoïde, sauf qu'au lieu d'avoir un type, nous avons un foncteur. C'est le même type d'algèbre, juste dans une catégorie différente. (C'est là que l'expression "une monade n'est qu'un monoïde dans la catégorie des endofoncteurs" vient pour autant que je sache.)

Maintenant, nous avons ces deux opérations: f ∘ f → fet Identity → f. Pour obtenir la coalgebra correspondante, il suffit de retourner les flèches. Cela nous donne deux nouvelles opérations: f → f ∘ fet f → Identity. Nous pouvons les transformer en types Haskell en ajoutant des variables de type comme ci-dessus, en nous donnant ∀α. f α → f (f α)et ∀α. f α → α. Cela ressemble à la définition d'une comonade:

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

Ainsi , un comonad est alors un cogèbre dans une catégorie de endofunctors.

Tikhon Jelvis
la source
45
C'est incroyablement précieux. J'avais réussi à brouiller certaines intuitions sur toute cette entreprise d'algèbre F à partir de la lecture et d'exemples (par exemple, en voyant leur utilisation avec les catamoprhismes), mais cela est tout simplement clair pour moi. Merci!
Luis Casillas
28
Ceci est une excellente explication.
Edward KMETT
5
@EdwardKmett: Merci. Les choses que j'ai ajoutées sur les classes et les objets sont-elles correctes? Je ne l'ai lu qu'aujourd'hui, mais cela semble logique.
Tikhon Jelvis
7
Pour ce que ça vaut: La "catégorie des endofoncteurs" est ici plus précisément une catégorie dont les objets sont des endofoncteurs sur une certaine catégorie et dont les flèches sont des transformations naturelles. Il s'agit d'une catégorie monoïdale, avec une composition de foncteur correspondant à (,)et le foncteur d'identité à (). Un objet monoïde dans une catégorie monoïdale est un objet avec des flèches correspondant à votre algèbre monoïde, qui décrit un objet monoïde dans Hask avec des types de produits comme structure monoïdale. Un objet monoïde dans la catégorie des endofoncteurs sur C est une monade sur C, donc oui, votre compréhension est correcte. :]
CA McCann
8
Ce fut une fin épique!
jdinunzio
85

Les algèbres F et les algèbres F sont des structures mathématiques qui contribuent au raisonnement sur les types inductifs (ou récursifs ).

Algèbres F

Nous allons commencer par les algèbres F. J'essaierai d'être aussi simple que possible.

Je suppose que vous savez ce qu'est un type récursif. Par exemple, il s'agit d'un type pour une liste d'entiers:

data IntList = Nil | Cons (Int, IntList)

Il est évident qu'il est récursif - en effet, sa définition se réfère à lui-même. Sa définition se compose de deux constructeurs de données, qui ont les types suivants:

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

Notez que j'ai écrit le type de Nilas () -> IntList, pas simplement IntList. Ce sont en fait des types équivalents du point de vue théorique, car le ()type n'a qu'un seul habitant.

Si nous écrivons des signatures de ces fonctions d'une manière plus théorique, nous obtiendrons

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

1est un ensemble d'unités (ensemble avec un élément) et l' A × Bopération est un produit croisé de deux ensembles Aet B(c'est-à-dire un ensemble de paires (a, b)apasse par tous les éléments de Aet bpasse par tous les éléments de B).

Union disjointe de deux ensembles Aet Best un ensemble A | Bqui est une union d'ensembles {(a, 1) : a in A}et {(b, 2) : b in B}. Essentiellement, il s'agit d'un ensemble de tous les éléments des deux Aet B, mais avec chacun de ces éléments `` marqués '' comme appartenant à l'un Aou à l'autre B, donc lorsque nous choisirons un élément de, A | Bnous saurons immédiatement si cet élément est venu de Aou de B.

Nous pouvons «joindre» Nilet Consfonctionner, afin qu'ils forment une seule fonction travaillant sur un ensemble 1 | (Int × IntList):

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

En effet, si la Nil|Consfonction est appliquée à la ()valeur (qui, évidemment, appartient à 1 | (Int × IntList)set), alors elle se comporte comme si elle l'était Nil; si Nil|Consest appliqué à n'importe quelle valeur de type (Int, IntList)(ces valeurs sont également dans l'ensemble 1 | (Int × IntList), il se comporte comme Cons.

Considérons maintenant un autre type de données:

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

Il a les constructeurs suivants:

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

qui peut également être réunie en une seule fonction:

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

On peut voir que ces deux joinedfonctions ont un type similaire: elles ressemblent toutes les deux

f :: F T -> T

Fest une sorte de transformation qui prend notre type et donne un type plus complexe, qui se compose de xet d' |opérations, d'usages Tet éventuellement d'autres types. Par exemple, pour IntListet IntTree Fressemble à ceci:

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

Nous pouvons immédiatement remarquer que tout type algébrique peut être écrit de cette façon. En effet, c'est pourquoi ils sont appelés «algébriques»: ils sont constitués d'un certain nombre de «sommes» (unions) et de «produits» (produits croisés) d'autres types.

Nous pouvons maintenant définir l'algèbre F. L'algèbre F est juste une paire (T, f), où Test un type et fest une fonction de type f :: F T -> T. Dans nos exemples, les algèbres F sont (IntList, Nil|Cons)et (IntTree, Leaf|Branch). Notez, cependant, que malgré ce type de ffonction est le même pour chaque F, Tet feux - mêmes peuvent être arbitraires. Par exemple, (String, g :: 1 | (Int x String) -> String)ou (Double, h :: Int | (Double, Double) -> Double)pour certains get hsont également des algèbres F pour le F. correspondant

Ensuite, nous pouvons introduire des homomorphismes d'algèbre F puis des algèbres F initiales , qui ont des propriétés très utiles. En fait, (IntList, Nil|Cons)est une algèbre F1 initiale, et (IntTree, Leaf|Branch)est une algèbre F2 initiale. Je ne présenterai pas les définitions exactes de ces termes et propriétés car ils sont plus complexes et abstraits que nécessaire.

Néanmoins, le fait que, disons, (IntList, Nil|Cons)est l'algèbre F nous permet de définir une foldfonction semblable à celle de ce type. Comme vous le savez, fold est une sorte d'opération qui transforme un type de données récursif en une valeur finie. Par exemple, nous pouvons replier une liste d'entiers en une seule valeur qui est une somme de tous les éléments de la liste:

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

Il est possible de généraliser une telle opération sur n'importe quel type de données récursif.

Ce qui suit est une signature de foldrfonction:

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

Notez que j'ai utilisé des accolades pour séparer les deux premiers arguments du dernier. Ce n'est pas une foldrfonction réelle , mais elle lui est isomorphe (c'est-à-dire que vous pouvez facilement obtenir l'une de l'autre et vice versa). Partiellement appliqué foldraura la signature suivante:

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

Nous pouvons voir que c'est une fonction qui prend une liste d'entiers et retourne un seul entier. Définissons une telle fonction en termes de notre IntListtype.

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

Nous voyons que cette fonction se compose de deux parties: la première partie définit le comportement de cette fonction en Nilpartie IntList, et la seconde partie définit le comportement de la fonction en Conspartie.

Supposons maintenant que nous programmons non pas en Haskell mais dans un langage qui permet l'utilisation de types algébriques directement dans les signatures de type (enfin, techniquement Haskell permet l'utilisation de types algébriques via des tuples et des Either a btypes de données, mais cela conduira à une verbosité inutile). Considérons une fonction:

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

On peut voir que reductorc'est une fonction de type F1 Int -> Int, tout comme dans la définition de l'algèbre F! En effet, la paire (Int, reductor)est une algèbre F1.

Parce qu'il IntLists'agit d'une algèbre F1 initiale, pour chaque type Tet pour chaque fonction, r :: F1 T -> Til existe une fonction, appelée catamorphisme pour r, qui se convertit IntListen T, et cette fonction est unique. En effet, dans notre exemple un catamorphisme pour reductorest sumFold. Notez comment reductoret sumFoldsont similaires: ils ont presque la même structure! L' utilisation en paramètre de reductordéfinition s(dont le type correspond à T) correspond à l'utilisation du résultat du calcul de sumFold xsen sumFolddéfinition.

Juste pour le rendre plus clair et vous aider à voir le motif, voici un autre exemple, et nous recommençons à partir de la fonction de pliage résultante. Considérons la appendfonction qui ajoute son premier argument au second:

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

Voici à quoi cela ressemble sur notre IntList:

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

Encore une fois, essayons d'écrire le réducteur:

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

appendFoldest un catamorphisme pour appendReductorlequel se transforme IntListen IntList.

Donc, essentiellement, les algèbres F nous permettent de définir des «replis» sur des infrastructures de données récursives, c'est-à-dire des opérations qui réduisent nos structures à une certaine valeur.

F-coalgebras

Les algèbres F sont des termes dits «doubles» pour les algèbres F. Ils nous permettent de définir unfoldspour les types de données récursifs, c'est-à-dire un moyen de construire des structures récursives à partir d'une certaine valeur.

Supposons que vous ayez le type suivant:

data IntStream = Cons (Int, IntStream)

Il s'agit d'un flux infini d'entiers. Son seul constructeur a le type suivant:

Cons :: (Int, IntStream) -> IntStream

Ou, en termes d'ensembles

Cons :: Int × IntStream -> IntStream

Haskell vous permet de faire correspondre les modèles sur les constructeurs de données, vous pouvez donc définir les fonctions suivantes en travaillant sur IntStreams:

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

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

Vous pouvez naturellement «joindre» ces fonctions en une seule fonction de type IntStream -> Int × IntStream:

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

Remarquez comment le résultat de la fonction coïncide avec la représentation algébrique de notre IntStreamtype. Une opération similaire peut également être effectuée pour d'autres types de données récursives. Vous avez peut-être déjà remarqué le motif. Je fais référence à une famille de fonctions de type

g :: T -> F T

Test un type. Désormais nous définirons

F1 T = Int × T

Maintenant, F-coalgebra est une paire (T, g), où Test un type et gest une fonction de type g :: T -> F T. Par exemple, (IntStream, head&tail)est un charbon-F1. Encore une fois, tout comme dans les algèbres F, get Tpeut être arbitraire, par exemple, (String, h :: String -> Int x String)est également un charbon-F1 pour quelque h.

Parmi tous les coalgebras F, il existe des soi-disant F-coalgebras terminaux , qui sont duaux des algèbres F initiales. Par exemple, IntStreamest un terminal F-coalgebra. Cela signifie que pour chaque type Tet pour chaque fonction, p :: T -> F1 Til existe une fonction, appelée anamorphisme , qui se transforme Ten IntStream, et cette fonction est unique.

Considérons la fonction suivante, qui génère un flux d'entiers successifs à partir de celui donné:

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

Inspectons maintenant une fonction natsBuilder :: Int -> F1 Int, c'est-à-dire natsBuilder :: Int -> Int × Int:

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

Encore une fois, nous pouvons voir une certaine similitude entre natset natsBuilder. Il est très similaire à la connexion que nous avons observée précédemment avec les réducteurs et les plis. natsest un anamorphisme pour natsBuilder.

Autre exemple, une fonction qui prend une valeur et une fonction et renvoie un flux d'applications successives de la fonction à la valeur:

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

Sa fonction de générateur est la suivante:

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

C'est alors iterateun anamorphisme pour iterateBuilder.

Conclusion

Donc, en bref, les algèbres F permettent de définir des plis, c'est-à-dire des opérations qui réduisent la structure récursive en une seule valeur, et les algèbres F permettent de faire le contraire: construire une structure [potentiellement] infinie à partir d'une seule valeur.

En fait, dans Haskell, les F-algèbres et les F-coalgebras coïncident. Il s'agit d'une très belle propriété qui est une conséquence de la présence de la valeur «bas» dans chaque type. Ainsi, dans Haskell, les plis et les dépliages peuvent être créés pour chaque type récursif. Cependant, le modèle théorique derrière cela est plus complexe que celui que j'ai présenté ci-dessus, donc je l'ai délibérément évité.

J'espère que cela t'aides.

Vladimir Matveev
la source
Le type et la définition de appendReductorsemble un peu étrange et ne m'a pas vraiment aidé à voir le modèle là-bas ... :) Pouvez-vous vérifier qu'il est correct? .. À quoi devraient ressembler les types de réducteurs en général? Dans la définition de rlà, est F1déterminé par l'IntList, ou est-ce un F arbitraire?
Max Galkin
37

Passer en revue le didacticiel Un didacticiel sur les (co) algèbres et (co) induction devrait vous donner un aperçu de la co-algèbre en informatique.

En voici une citation pour vous convaincre,

En termes généraux, un programme dans un langage de programmation manipule des données. Au cours du développement de l'informatique au cours des dernières décennies, il est devenu clair qu'une description abstraite de ces données est souhaitable, par exemple pour s'assurer que son programme ne dépend pas de la représentation particulière des données sur lesquelles il opère. En outre, un tel caractère abstrait facilite les preuves d'exactitude.
Ce désir a conduit à l'utilisation de méthodes algébriques en informatique, dans une branche appelée spécification algébrique ou théorie des types de données abstraits. Les objets d'étude sont des types de données en eux-mêmes, utilisant des notions de techniques familières à l'algèbre. Les types de données utilisés par les informaticiens sont souvent générés à partir d'une collection donnée d'opérations (constructeurs), et c'est pour cette raison que l '"initialité" des algèbres joue un rôle si important.
Les techniques algébriques standard se sont révélées utiles pour capturer divers aspects essentiels des structures de données utilisées en informatique. Mais il s'est avéré difficile de décrire algébriquement certaines des structures intrinsèquement dynamiques présentes dans l'informatique. De telles structures impliquent généralement une notion d'État, qui peut être transformée de diverses manières. Les approches formelles de ces systèmes dynamiques basés sur des états utilisent généralement des automates ou des systèmes de transition, comme premières références classiques.
Au cours de la dernière décennie, l'idée a progressivement grandi que de tels systèmes basés sur l'état ne devraient pas être décrits comme des algèbres, mais comme des soi-disant algèbres. Il s'agit du double formel des algèbres, d'une manière qui sera précisée dans ce tutoriel. La double propriété de l '"initialité" pour les algèbres, à savoir la finalité, s'est avérée cruciale pour ces co-algèbres. Et le principe de raisonnement logique qui est nécessaire pour de telles co-algèbres finales n'est pas l'induction mais la co-induction.


Prélude, sur la théorie des catégories. La théorie des catégories devrait être renommée théorie des foncteurs. Les catégories sont ce que l'on doit définir pour définir les foncteurs. (De plus, les foncteurs sont ce qu'il faut définir pour définir les transformations naturelles.)

Qu'est-ce qu'un foncteur? C'est une transformation d'un ensemble à un autre qui préserve leur structure. (Pour plus de détails, il y a beaucoup de bonnes descriptions sur le net).

Qu'est-ce qu'une algèbre F? C'est l'algèbre du foncteur. C'est juste l'étude de la propriété universelle du foncteur.

Comment peut-il être lié à l'informatique? Le programme peut être vu comme un ensemble structuré d'informations. L'exécution du programme correspond à la modification de cet ensemble structuré d'informations. Il semble bon que l'exécution conserve la structure du programme. L'exécution peut alors être vue comme l'application d'un foncteur sur cet ensemble d'informations. (Celui définissant le programme).

Pourquoi F-co-algèbre? Les programmes sont par essence duels car ils sont décrits par des informations et ils agissent en conséquence. Ensuite, principalement les informations qui composent le programme et les font changer peuvent être visualisées de deux manières.

  • Données pouvant être définies comme les informations traitées par le programme.
  • État qui peut être défini comme l'information partagée par le programme.

À ce stade, je voudrais dire que,

  • La F-algèbre est l'étude de la transformation fonctorielle agissant sur l'Univers de données (tel que défini ici).
  • Les F-co-algèbres sont l'étude de la transformation fonctorielle agissant sur l'Univers de l'Etat (tel que défini ici).

Pendant la durée de vie d'un programme, les données et l'état coexistent et se complètent. Ils sont doubles.

zurgl
la source
5

Je vais commencer par des trucs qui sont évidemment liés à la programmation, puis ajouter quelques trucs mathématiques, pour les garder aussi concrets et terre-à-terre que possible.


Citons quelques informaticiens sur la coinduction…

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

L'induction concerne les données finies, la co-induction concerne les données infinies.

L'exemple typique de données infinies est le type d'une liste paresseuse (un flux). Par exemple, disons que nous avons en mémoire l'objet suivant:

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

L'ordinateur ne peut pas contenir la totalité de π, car il n'a qu'une quantité limitée de mémoire! Mais ce qu'il peut faire, c'est maintenir un programme fini, qui produira toute expansion arbitrairement longue de π que vous désirez. Tant que vous n'utilisez que des éléments finis de la liste, vous pouvez calculer avec cette liste infinie autant de fois que vous en avez besoin.

Cependant, considérez le programme suivant:

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


 print_third_element pi

Ce programme devrait imprimer le troisième chiffre de pi. Mais dans certains langages, tout argument d'une fonction est évalué avant d'être passé dans une fonction (strict, pas paresseux, évaluation). Si nous utilisons cet ordre de réduction, notre programme ci-dessus fonctionnera pour toujours en calculant les chiffres de pi avant qu'il ne puisse être transmis à notre fonction d'imprimante (ce qui n'arrive jamais). Étant donné que la machine n'a pas de mémoire infinie, le programme finira par manquer de mémoire et se bloquer. Ce n'est peut-être pas le meilleur ordre d'évaluation.

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

Dans les langages de programmation fonctionnels paresseux comme Haskell, les structures de données infinies sont partout. Des listes infinies et des types de données plus exotiques fournissent des abstractions pratiques pour la communication entre les parties d'un programme. Atteindre une commodité similaire sans structures paresseuses infinies nécessiterait, dans de nombreux cas, des inversions acrobatiques du flux de contrôle.

http://www.alexandrasilva.org/#/talks.html exemples de coalgebras par Alexandra Silva


Relier le contexte mathématique ambiant aux tâches de programmation habituelles

Qu'est-ce qu'une "algèbre"?

Les structures algébriques ressemblent généralement à:

  1. Des trucs
  2. Ce que les choses peuvent faire

Cela devrait ressembler à des objets avec 1. propriétés et 2. méthodes. Ou encore mieux, cela devrait ressembler à des signatures de type.

Les exemples mathématiques standard incluent monoïde ⊃ groupe ⊃ espace vectoriel ⊃ "une algèbre". Les monoïdes sont comme des automates: des séquences de verbes (par exemple, f.g.h.h.nothing.f.g.f). Un gitjournal qui ajoute toujours l'historique et ne le supprime jamais serait un monoïde mais pas un groupe. Si vous ajoutez des inverses (par exemple, des nombres négatifs, des fractions, des racines, la suppression de l'historique accumulé, la destruction d'un miroir brisé), vous obtenez un groupe.

Les groupes contiennent des éléments qui peuvent être ajoutés ou soustraits ensemble. Par exemple, les Durations peuvent être ajoutés ensemble. (Mais ce Daten'est pas le cas.) Les durées vivent dans un espace vectoriel (pas seulement un groupe) car elles peuvent également être mises à l'échelle par des nombres extérieurs. (Une signature de type de scaling :: (Number,Duration) → Duration.)

Les algèbres ⊂ les espaces vectoriels peuvent faire encore autre chose: il y en a m :: (T,T) → T. Appelez cela "multiplication" ou pas, car une fois que vous partez, Integersil est moins évident de savoir ce que devrait être la "multiplication" (ou "exponentiation" ).

(C'est pourquoi les gens se tournent vers les propriétés universelles (théoriques des catégories): pour leur dire ce que la multiplication devrait faire ou être :

propriété universelle du produit )


Algèbres → Coalgebras

La comultiplication est plus facile à définir d'une manière qui ne semble pas arbitraire, que la multiplication, car pour partir de là, T → (T,T)il suffit de répéter le même élément. ("carte diagonale" - comme les matrices / opérateurs diagonaux dans la théorie spectrale)

Counité est généralement la trace (somme des entrées en diagonale), bien qu'encore une fois ce qui est important est ce que votre counité fait ; traceest juste une bonne réponse pour les matrices.

La raison de regarder un double espace , en général, est parce qu'il est plus facile de penser dans cet espace. Par exemple, il est parfois plus facile de penser à un vecteur normal qu'au plan auquel il est normal, mais vous pouvez contrôler les avions (y compris les hyperplans) avec des vecteurs (et maintenant je parle du vecteur géométrique familier, comme dans un traceur de rayons) .


Apprivoiser des données (non) structurées

Les mathématiciens peuvent modéliser quelque chose d'amusant comme celui de TQFT , tandis que les programmeurs doivent lutter avec

  • dates / heures ( + :: (Date,Duration) → Date),
  • des lieux ( Paris(+48.8567,+2.3508)! C'est une forme, pas un point.),
  • JSON non structuré qui est censé être cohérent dans un certain sens,
  • XML erroné mais proche,
  • des données SIG incroyablement complexes qui devraient satisfaire des charges de relations sensibles,
  • des expressions régulières qui signifiaient quelque chose pour vous, mais qui signifient beaucoup moins pour perl.
  • CRM qui devrait contenir tous les numéros de téléphone du directeur et les emplacements des villas, son (maintenant ex-) noms de femme et d'enfants, son anniversaire et tous les cadeaux précédents, chacun devant satisfaire des relations "évidentes" (évidentes pour le client) qui sont incroyablement difficile à coder,
  • .....

Les informaticiens, lorsqu'ils parlent de houillères, ont généralement à l'esprit des opérations de configuration, comme les produits cartésiens. Je crois que c'est ce que les gens veulent dire quand ils disent "Les algèbres sont des houillères à Haskell". Mais dans la mesure où les programmeurs doivent modéliser complexes des types de données comme Place, Date/Timeet Customer-et font ces modèles ressemblent autant le monde réel (ou du moins vue de l'utilisateur final du monde réel) que possible, je crois duales, pourrait être utile au-delà du seul monde défini.

isomorphismes
la source