Que sont les monades gratuites?

369

Je l' ai vu le terme gratuit Monad pop - up chaque maintenant et puis pendant un certain temps, mais tout le monde semble juste d'utiliser / en discuter sans donner une explication de ce qu'ils sont. Alors: que sont les monades gratuites? (Je dirais que je connais les monades et les bases de Haskell, mais que je n'ai qu'une connaissance très approximative de la théorie des catégories.)

David
la source
12
Une assez bonne explication est ici haskellforall.com/2012/06/…
Roger Lindsjö
19
@Roger, c'est le genre de page qui m'a amené ici. Pour moi, cet exemple définit une instance de monade pour un type nommé "Free" et c'est tout.
David

Réponses:

296

La réponse d'Edward Kmett est évidemment excellente. Mais c'est un peu technique. Voici une explication peut-être plus accessible.

Les monades gratuites ne sont qu'un moyen général de transformer des foncteurs en monades. Autrement dit, étant donné que tout foncteur f Free fest une monade. Ce ne serait pas très utile, sauf si vous obtenez une paire de fonctions

liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r) -> Free f r -> r

le premier vous permet de "rentrer" dans votre monade, et le second vous donne un moyen de "sortir" de celle-ci.

Plus généralement, si X est un Y avec quelques trucs supplémentaires P, alors un "X libre" est un moyen de passer d'un Y à un X sans rien gagner de plus.

Exemples: un monoïde (X) est un ensemble (Y) avec une structure supplémentaire (P) qui dit essentiellement qu'il a une opération (vous pouvez penser à l'addition) et une certaine identité (comme zéro).

Donc

class Monoid m where
   mempty  :: m
   mappend :: m -> m -> m

Maintenant, nous connaissons tous des listes

data [a] = [] | a : [a]

Eh bien, étant donné tout type, tnous savons que [t]c'est un monoïde

instance Monoid [t] where
  mempty   = []
  mappend = (++)

et ainsi les listes sont le "monoïde libre" sur les ensembles (ou dans les types Haskell).

D'accord, donc les monades gratuites sont la même idée. Nous prenons un foncteur et rendons une monade. En fait, comme les monades peuvent être considérées comme des monoïdes dans la catégorie des endofoncteurs, la définition d'une liste

data [a] = [] | a : [a]

ressemble beaucoup à la définition des monades libres

data Free f a = Pure a | Roll (f (Free f a))

et l' Monadinstance a une similitude avec l' Monoidinstance pour les listes

--it needs to be a functor
instance Functor f => Functor (Free f) where
  fmap f (Pure a) = Pure (f a)
  fmap f (Roll x) = Roll (fmap (fmap f) x)

--this is the same thing as (++) basically
concatFree :: Functor f => Free f (Free f a) -> Free f a
concatFree (Pure x) = x
concatFree (Roll y) = Roll (fmap concatFree y)

instance Functor f => Monad (Free f) where
  return = Pure -- just like []
  x >>= f = concatFree (fmap f x)  --this is the standard concatMap definition of bind

maintenant, nous obtenons nos deux opérations

-- this is essentially the same as \x -> [x]
liftFree :: Functor f => f a -> Free f a
liftFree x = Roll (fmap Pure x)

-- this is essentially the same as folding a list
foldFree :: Functor f => (f r -> r) -> Free f r -> r
foldFree _ (Pure a) = a
foldFree f (Roll x) = f (fmap (foldFree f) x)
Philip JF
la source
12
C'est peut-être la meilleure explication accessible de "gratuit" que j'ai jamais vue. Surtout le paragraphe commençant par «Plus généralement».
John L
16
Je pense qu'il est intéressant de regarder Free f a = Pure a | Roll (f (Free f a))comme Free f a = a + fa + ffa + ..., c'est-à-dire "f appliqué à un certain nombre de fois". Ensuite concatFree(c'est-à-dire join) prend un "f appliqué un nombre illimité de fois à (f appliqué un nombre illimité de fois à un)" et réduit les deux applications imbriquées en une seule. Et >>=prend "f appliqué un certain nombre de fois à un" et "comment aller de a à (b avec f appliqué un certain nombre de fois)", et applique fondamentalement ce dernier aux a à l'intérieur du premier et effondre l'imbrication. Maintenant, je comprends moi-même!
jkff
1
est concatFreefondamentalement join?
rgrinberg
11
«Voici une explication peut-être plus accessible. […] En fait, étant donné que les monades peuvent être considérées comme des monoïdes dans la catégorie des foncteurs endo,… »Néanmoins, je pense que c'est une très bonne réponse.
Ruud
2
"les monades peuvent être considérées comme des monoïdes dans la catégorie des foncteurs endo" <3 (vous devez créer un lien vers stackoverflow.com/a/3870310/1306877 parce que chaque haskeller devrait connaître cette référence!)
tlo
419

Voici une réponse encore plus simple: Une Monade est quelque chose qui "calcule" lorsque le contexte monadique est réduit par join :: m (m a) -> m a(rappelant que cela >>=peut être défini comme x >>= y = join (fmap y x)). C'est ainsi que les Monades transportent le contexte à travers une chaîne séquentielle de calculs: car à chaque point de la série, le contexte de l'appel précédent est réduit au suivant.

Une monade libre satisfait à toutes les lois de la monade, mais ne fait pas d'effondrement (c.-à-d. Calcul). Il crée simplement une série de contextes imbriqués. L'utilisateur qui crée une telle valeur monadique libre est responsable de faire quelque chose avec ces contextes imbriqués, de sorte que la signification d'une telle composition puisse être différée jusqu'à ce que la valeur monadique ait été créée.

John Wiegley
la source
8
Vos paragraphes sont un excellent ajout au message de Philip.
David
20
J'aime vraiment cette réponse.
danidiaz
5
La monade gratuite peut-elle remplacer la classe de type Monade? Autrement dit, pourrais-je écrire un programme en utilisant uniquement le retour et la liaison de la monade libre, puis joindre les résultats en utilisant ce que je préfère de Mwybe ou List ou autre, ou même générer plusieurs vues monadiques d'une séquence d'appels de fonction liés / concattés. Ignorer le fond et la non-terminaison, c'est-à-dire.
misterbee
2
Cette réponse a aidé, mais je pense que cela m'aurait confondu si je n'avais pas rencontré «rejoindre» sur le cours NICTA et lu haskellforall.com/2012/06/… . Donc pour moi, l'astuce pour comprendre est de lire beaucoup de réponses jusqu'à ce qu'elles s'enfoncent. (Référence NICTA: github.com/NICTA/course/blob/master/src/Course/Bind.hs )
Martin Capodici
1
cette réponse est meilleure que jamais
Curycu
143

Un foo gratuit se trouve être la chose la plus simple qui satisfait toutes les lois du foo. C'est-à-dire qu'il satisfait exactement aux lois nécessaires pour être un fou et rien de plus.

Un foncteur oublieux est celui qui «oublie» une partie de la structure en passant d'une catégorie à l'autre.

Des foncteurs donnés F : D -> C, et G : C -> D, disons-nous F -| G, Fsont adjoints à gauche Gou Gsont adjoints à droite Fchaque fois que tout a, b: F a -> best isomorphe à a -> G b, où les flèches proviennent des catégories appropriées.

Formellement, un foncteur libre est laissé adjoint à un foncteur oublieux.

Le monoïde gratuit

Commençons par un exemple plus simple, le monoïde libre.

Prenez un monoïde, qui est défini par un ensemble de support T, une fonction binaire pour écraser une paire d'éléments ensemble f :: T → T → T, et unit :: T, de sorte que vous avez une loi associative, et une loi d'identité: f(unit,x) = x = f(x,unit).

Vous pouvez créer un foncteur à Upartir de la catégorie des monoïdes (où les flèches sont des homomorphismes monoïdes, c'est-à-dire qu'ils s'assurent qu'ils correspondent unità unitl'autre monoïde et que vous pouvez composer avant ou après le mappage à l'autre monoïde sans changer de signification) à la catégorie d'ensembles (où les flèches ne sont que des flèches de fonction) qui «oublie» l'opération et unit, et vous donne simplement l'ensemble de support.

Ensuite, vous pouvez définir un foncteur Fde la catégorie des ensembles à la catégorie des monoïdes qui reste adjointe à ce foncteur. Ce foncteur est le foncteur qui associe un ensemble aau monoïde [a], où unit = []et mappend = (++).

Donc, pour passer en revue notre exemple jusqu'à présent, en pseudo-Haskell:

U : Mon  Set -- is our forgetful functor
U (a,mappend,mempty) = a

F : Set  Mon -- is our free functor
F a = ([a],(++),[])

Ensuite, pour montrer que Fc'est gratuit, nous devons démontrer qu'il est laissé adjoint à U, un foncteur oublieux, c'est-à-dire, comme nous l'avons mentionné ci-dessus, nous devons montrer que

F a → b est isomorphe à a → U b

maintenant, rappelez-vous que la cible de Fest dans la catégorie Mondes monoïdes, où les flèches sont des homomorphismes monoïdes, nous avons donc besoin d'un pour montrer qu'un homomorphisme monoïde de [a] → bpeut être décrit précisément par une fonction de a → b.

Dans Haskell, nous appelons le côté de cela qui vit dans Set(euh, Haskla catégorie de types Haskell que nous prétendons être Set), juste foldMap, qui, lorsqu'elle est spécialisée de Data.Foldableà Lists, a du type Monoid m => (a → m) → [a] → m.

Il y a des conséquences qui découlent de cette adjonction. Notamment, si vous oubliez, puis construisez avec free, puis oubliez à nouveau, c'est comme vous l'avez oublié une fois, et nous pouvons l'utiliser pour construire la jointure monadique. depuis UFUF~ U(FUF)~ UF, et nous pouvons passer dans l'homomorphisme monoïde d'identité de [a]à [a]travers l'isomorphisme qui définit notre adjonction, obtenir qu'un isomorphisme de liste [a] → [a]est une fonction de type a -> [a], et c'est juste un retour pour les listes.

Vous pouvez composer tout cela plus directement en décrivant une liste en ces termes avec:

newtype List a = List (forall b. Monoid b => (a -> b) -> b)

La monade libre

Qu'est-ce qu'une Free Monad ?

Eh bien, nous faisons la même chose que nous faisions avant, nous commençons avec un foncteur oublieux U de la catégorie des monades où les flèches sont des homomorphismes de monade à une catégorie d'endofoncteurs où les flèches sont des transformations naturelles, et nous recherchons un foncteur qui est laissé adjoint pour que.

Alors, comment cela se rapporte-t-il à la notion de monade libre telle qu'elle est généralement utilisée?

Savoir que quelque chose est une monade libre, Free fvous dit que donner un homomorphisme de monade à partir de Free f -> m, c'est la même chose (isomorphe à) que donner une transformation naturelle (un homomorphisme de foncteur) à partir de f -> m. Rappelez-vous que F a -> bdoit être isomorphe pour a -> U bque F soit laissé adjoint à U. U ici mappé des monades aux foncteurs.

F est au moins isomorphe au Freetype que j'utilise dans mon freepackage sur le piratage.

Nous pourrions également le construire dans une analogie plus étroite avec le code ci-dessus pour la liste gratuite, en définissant

class Algebra f x where
  phi :: f x -> x

newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x)

Cofree Comonads

Nous pouvons construire quelque chose de similaire, en regardant l'adjoint de droite d'un foncteur oublieux en supposant qu'il existe. Un foncteur cofree est simplement / à droite adjoint / à un foncteur oublieux, et par symétrie, savoir que quelque chose est un comonad cofree revient à savoir que donner un homomorphisme comonad à partir de w -> Cofree fest la même chose que donner une transformation naturelle à partir de w -> f.

Edward KMETT
la source
12
@PauloScardine, vous n'avez rien à craindre. Ma question est venue de l'intérêt de comprendre une structure de données avancée et peut-être d'avoir un aperçu de ce qui est à la pointe du développement Haskell en ce moment - ce n'est en aucun cas nécessaire ou représentatif de ce qu'est réellement l'écriture de Haskell jusqu'à présent. (Et attention, ça va mieux une fois que vous avez passé la phase d'apprentissage IO)
David
8
@PauloScardine Vous n'avez pas besoin de la réponse ci-dessus pour programmer de manière productive en Haskell, même avec des monades gratuites. En fait, je ne recommanderais pas d'attaquer la monade gratuite de cette façon à quelqu'un qui n'a pas de formation en théorie des catégories. Il existe de nombreuses façons d'en parler d'un point de vue opérationnel et d'explorer comment l'utiliser sans plonger dans la théorie des catégories. Cependant, il m'est impossible de répondre à la question de savoir d'où ils viennent sans plonger dans la théorie. Les constructions libres sont un outil puissant dans la théorie des catégories, mais vous n'avez pas besoin de ce contexte pour les utiliser.
Edward KMETT
18
@PauloScardine: Vous n'avez besoin d'aucun calcul pour utiliser Haskell efficacement et même comprendre ce que vous faites. C'est un peu bizarre de se plaindre que "cette langue est mathématique" quand la mathyness est juste une bonté supplémentaire que vous pouvez utiliser pour le plaisir et le profit. Vous n'obtenez pas ces choses dans la plupart des langues impératives. Pourquoi vous plaindriez-vous des extras? Vous pouvez simplement choisir de ne PAS raisonner mathématiquement et de l'aborder comme vous le feriez avec n'importe quel autre nouveau langage.
Sarah
3
@Sarah: Je n'ai pas encore vu un document ou une conversation IRC sur haskell qui ne soit pas lourd sur la théorie informatique et les thermes de calcul lambda.
Paulo Scardine
11
@PauloScardine, cela dérive un peu OT, mais pour la défense de Haskell: des choses techniques similaires s'appliquent à tous les autres langages de programmation, sauf que Haskell a une compilation si agréable que les gens peuvent vraiment en parler. Pourquoi / comment X est une monade est intéressant pour beaucoup de gens, les discussions sur la norme à virgule flottante IEEE ne le sont pas; les deux cas n'ont pas d'importance pour la plupart des gens, car ils peuvent simplement utiliser les résultats.
David
72

La Free Monad (structure de données) est à la Monad (classe) comme la List (structure de données) à Monoid (classe): c'est l'implémentation triviale, où vous pouvez décider ensuite comment le contenu sera combiné.


Vous savez probablement ce qu'est une Monade et que chaque Monade a besoin d'une implémentation spécifique (respectant la loi de la Monade) de fmap+ join+ returnou bind+ return.

Supposons que vous ayez un Functor (une implémentation de fmap) mais le reste dépend des valeurs et des choix effectués au moment de l'exécution, ce qui signifie que vous voulez pouvoir utiliser les propriétés Monad mais que vous souhaitez choisir les fonctions Monad par la suite.

Cela peut être fait en utilisant la Free Monad (structure de données), qui enveloppe le Functor (type) de manière à ce que ce joinsoit plutôt un empilement de ces foncteurs qu'une réduction.

Le réel returnet celui que joinvous souhaitez utiliser peuvent désormais être donnés comme paramètres à la fonction de réduction foldFree:

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a

Pour expliquer les types, nous pouvons remplacer Functor fpar Monad met bpar (m a):

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)
comonad
la source
8
Cette réponse m'a donné l'impression que je comprends à quoi ils pourraient même être utiles.
David
59

Une monade libre Haskell est une liste de foncteurs. Comparer:

data List a   = Nil    | Cons  a (List a  )

data Free f r = Pure r | Free (f (Free f r))

Pureest analogue à Nilet Freeest analogue à Cons. Une monade gratuite stocke une liste de foncteurs au lieu d'une liste de valeurs. Techniquement, vous pouvez implémenter des monades gratuites en utilisant un type de données différent, mais toute implémentation doit être isomorphe à celle ci-dessus.

Vous utilisez des monades gratuites chaque fois que vous avez besoin d'un arbre de syntaxe abstrait. Le foncteur de base de la monade libre est la forme de chaque étape de l'arbre de syntaxe.

Mon article , que quelqu'un a déjà lié, donne plusieurs exemples de la façon de construire des arbres de syntaxe abstraits avec des monades gratuites

Gabriel Gonzalez
la source
6
Je sais que vous étiez en train de faire une analogie plutôt que de faire une définition, mais une monade libre n'est certainement pas analogue à une liste de foncteurs en aucun sens. C'est beaucoup plus proche d'un arbre de foncteurs.
Tom Ellis
6
Je maintiens ma terminologie. Par exemple, en utilisant mon package index-core, vous pouvez définir des "compréhensions de monade libres", qui se comportent exactement comme la monade de liste, sauf que vous liez des foncteurs au lieu de valeurs. Une monade gratuite est une liste de foncteurs en ce sens que si vous traduisez tous les concepts Haskell dans la catégorie des foncteurs, les listes deviennent des monades libres. Un véritable arbre de foncteurs devient alors quelque chose de complètement différent.
Gabriel Gonzalez
4
Vous avez raison de dire que la monade est la catégorisation, dans un certain sens, du concept de monoïde, donc les monades libres sont analogues aux monoïdes libres, c'est-à-dire les listes. Dans cette mesure, vous avez certainement raison. Cependant la structure d'une valeur d'une monade libre n'est pas une liste. C'est un arbre, comme je le détaille ci-dessous .
Tom Ellis
2
@TomEllis Techniquement, ce n'est qu'un arbre si votre foncteur de base est un foncteur produit. Lorsque vous avez un foncteur de somme comme foncteur de base, il ressemble plus à une machine à empiler.
Gabriel Gonzalez
21

Je pense qu'un simple exemple concret sera utile. Supposons que nous ayons un foncteur

data F a = One a | Two a a | Two' a a | Three Int a a a

avec l'évidence fmap. Ensuite , Free F aest le type d'arbres dont les feuilles ont le type aet dont les noeuds sont étiquetés avec One, Two, Two'et Three. One-les nœuds ont un enfant,Two - et Two'-nodes ont deux enfants et Three-nodes en ont trois et sont également étiquetés avec un Int.

Free Fest une monade. returncorrespond xà l'arbre qui n'est qu'une feuille avec une valeurx . t >>= fregarde chacune des feuilles et les remplace par des arbres. Lorsque la feuille a une valeur, yelle remplace cette feuille par l'arbre f y.

Un diagramme rend cela plus clair, mais je n'ai pas les moyens d'en dessiner facilement un!

Tom Ellis
la source
14
Ce que vous dites, c'est que la monade libre prend la forme du foncteur lui-même. Donc, si le foncteur ressemble à un arbre (produits), la monade libre ressemble à un arbre; si elle ressemble à une liste (sommes), la monade libre ressemble à une liste; si c'est comme une fonction, la monade libre est comme une fonction; etc. Cela a du sens pour moi. Donc, tout comme dans un monoïde gratuit, vous continuez à traiter chaque application de mappend comme créant un élément complètement nouveau; en monade libre, vous traitez chaque application du foncteur comme un élément complètement nouveau.
Bartosz Milewski
4
Même si le foncteur est un "foncteur de somme", la monade libre qui en résulte est toujours arborescente. Vous vous retrouvez avec plus d'un type de nœud dans votre arborescence: un pour chaque composante de votre somme. Si votre "foncteur de somme" est X -> 1 + X, alors vous obtenez en effet une liste, qui n'est qu'une sorte d'arbre dégénéré.
Tom Ellis