O que são mônadas gratuitas?

368

Eu vi o termo gratuito Mônada pop up a cada agora e , em seguida, por algum tempo, mas todo mundo só parece usar / discuti-los sem dar uma explicação sobre o que são. Então: o que são mônadas gratuitas? (Eu diria que estou familiarizado com as mônadas e os conceitos básicos de Haskell, mas tenho apenas um conhecimento muito rudimentar da teoria das categorias.)

David
fonte
12
Uma explicação razoavelmente boa está aqui haskellforall.com/2012/06/…
Roger Lindsjö 12/11/2012
19
@ Roger, esse é o tipo de página que me trouxe aqui. Para mim, esse exemplo define uma instância de mônada para um tipo chamado "Free" e é isso.
David

Respostas:

295

A resposta de Edward Kmett é obviamente ótima. Mas, é um pouco técnico. Aqui está uma explicação talvez mais acessível.

Mônadas livres são apenas uma maneira geral de transformar functores em mônadas. Ou seja, dado que qualquer functor f Free fé uma mônada. Isso não seria muito útil, exceto se você obtiver um par de funções

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

o primeiro deles permite que você "entre" em sua mônada, e o segundo fornece uma maneira de "sair" dela.

De maneira mais geral, se X é um Y com algumas coisas extras P, então um "X livre" é uma maneira de passar de um Y para um X sem ganhar nada extra.

Exemplos: um monóide (X) é um conjunto (Y) com estrutura extra (P) que basicamente diz que possui uma operação (você pode pensar em adição) e alguma identidade (como zero).

assim

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

Agora, todos nós sabemos listas

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

Bem, dado qualquer tipo t, sabemos que [t]é um monóide

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

e assim as listas são o "monóide livre" sobre os conjuntos (ou nos tipos Haskell).

Ok, então mônadas gratuitas são a mesma ideia. Pegamos um functor e devolvemos uma mônada. De fato, como as mônadas podem ser vistas como monóides na categoria de endofunitores, a definição de uma lista

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

parece muito com a definição de mônadas livres

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

e a Monadinstância tem uma semelhança com a Monoidinstância para listas

--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

agora, temos nossas duas operações

-- 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
fonte
12
Essa pode ser a melhor explicação acessível de "gratuito" que eu já vi. Especialmente o parágrafo que começa com "Mais geralmente".
John L
16
Eu acho interessante olhar Free f a = Pure a | Roll (f (Free f a))como Free f a = a + fa + ffa + ..., ou seja, "f aplicado a inúmeras vezes". Então concatFree(ie join) pega um "f aplicado qualquer número de vezes em (f aplicado qualquer número de vezes em a)" e recolhe os dois aplicativos aninhados em um. E >>=pega "f aplicado inúmeras vezes a a" e "como passar de a a (b com f aplicado qualquer número de vezes)" e basicamente aplica o último a a dentro do primeiro e fecha o aninhamento. Agora eu mesmo entendo!
Jkff #
11
é concatFreebasicamente join?
rgrinberg
11
“Aqui está uma explicação talvez mais acessível. […] De fato, uma vez que as mônadas podem ser vistas como monóides na categoria de endo functores,… ”No entanto, acho que essa é uma resposta muito boa.
Ruud
2
"mônadas pode ser visto como monoids na categoria de endo functors" <3 (você deve conectar-se a stackoverflow.com/a/3870310/1306877 porque cada haskeller deve saber sobre essa referência!)
TLO
418

Aqui está uma resposta ainda mais simples: Uma Mônada é algo que "calcula" quando o contexto monádico é recolhido join :: m (m a) -> m a(lembrando que >>=pode ser definido como x >>= y = join (fmap y x)). É assim que as Mônadas carregam o contexto através de uma cadeia seqüencial de cálculos: porque em cada ponto da série, o contexto da chamada anterior é recolhido com o próximo.

Uma mônada livre satisfaz todas as leis da Mônada, mas não entra em colapso (isto é, computação). Ele apenas cria uma série aninhada de contextos. O usuário que cria um valor monádico gratuito é responsável por fazer algo com esses contextos aninhados, para que o significado de uma composição possa ser adiado até depois que o valor monádico for criado.

John Wiegley
fonte
8
Seus parágrafos são realmente um ótimo complemento para o post de Philip.
David
20
Eu realmente gosto desta resposta.
Danidiaz 15/03
5
A mônada livre pode substituir a classe do tipo Mônada? Ou seja, eu poderia escrever um programa usando apenas o retorno e a ligação da mônada livre e depois juntar os resultados usando o que eu preferir de Mwybe ou List ou o que quer que seja, ou mesmo gerar várias visualizações monádicas de uma sequência de chamadas de função ligadas / concatenadas. Ignorar a parte inferior e o não-término, isto é.
misterbee
2
Essa resposta ajudou, mas acho que teria me confundido se eu não tivesse encontrado 'participar' do curso da NICTA e lido haskellforall.com/2012/06/… . Então, para mim, o truque para entender é ler muitas respostas até ele aparecer. (Referência do NICTA : github.com/NICTA/course/blob/master/src/Course/Bind.hs )
Martin Capodici
11
esta resposta é a melhor de sempre
Curycu
142

Um foo livre passa a ser a coisa mais simples que satisfaz todas as leis 'foo'. Isto é, satisfaz exatamente as leis necessárias para ser um foo e nada extra.

Um functor esquecido é aquele que "esquece" parte da estrutura à medida que passa de uma categoria para outra.

Dado functors F : D -> C, e G : C -> D, dizemos F -| G, Fé deixado adjacente a G, ou Gé adjacente a todo momento em Fque todos a, b: F a -> bé isomórfico para a -> G b, onde as setas vêm das categorias apropriadas.

Formalmente, um functor livre fica adjacente a um functor esquecido.

O Monóide Livre

Vamos começar com um exemplo mais simples, o monóide livre.

Dê uma monoid, que é definida por um conjunto transportador T, uma função binário para esmagar um par de elementos juntos f :: T → T → T, e uma unit :: T, de modo que você tem uma lei associativa, e uma lei de identidade: f(unit,x) = x = f(x,unit).

Você pode fazer um functor Uda categoria de monoids (onde flechas são homomorphisms monoid, isto é, garantir que eles mapear unita unitdo outro monoid, e que você pode compor antes ou após o mapeamento para o outro monoid sem alterar o significado) à categoria de conjuntos (onde as setas são apenas setas funcionais) que 'esquecem' da operação e unit, e apenas fornecem o conjunto da transportadora.

Em seguida, você pode definir um functor Fda categoria de conjuntos para a categoria de monoides que fica adjacente a esse functor. Esse functor é o functor que mapeia um conjunto apara o monóide [a], onde unit = [], e mappend = (++).

Então, para revisar nosso exemplo até agora, em pseudo-Haskell:

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

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

Então, para mostrar Fé gratuito, precisamos demonstrar que ele fica adjacente a Uum functor esquecido, ou seja, como mencionamos acima, precisamos mostrar que

F a → b é isomórfico para a → U b

Agora, lembre-se de que o alvo de Festá na categoria Monde monóides, onde as setas são homomorfismos monóides; portanto, precisamos mostrar que um homomorfismo monóide de [a] → bpode ser descrito precisamente por uma função de a → b.

Em Haskell, chamamos o lado disso que vive em Set(er, Haska categoria de tipos de Haskell que pretendemos ser Set), just foldMap, que quando especializada de Data.Foldablepara Lists tem tipo Monoid m => (a → m) → [a] → m.

Há conseqüências que se seguem, sendo uma adjunção. Notavelmente, se você esquecer, construa gratuitamente, e esqueça novamente, é como você esqueceu uma vez, e podemos usar isso para criar a junção monádica. desde UFUF~ U(FUF)~ UF, e podemos passar o homomorfismo monóide de identidade de [a]para [a]o isomorfismo que define nossa adjunção, entender que um isomorfismo de lista [a] → [a]é uma função do tipo a -> [a], e isso é apenas um retorno para listas.

Você pode compor tudo isso mais diretamente, descrevendo uma lista nestes termos com:

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

The Free Monad

Então, o que é uma Mônada Livre ?

Bem, fazemos o mesmo que fizemos antes, começamos com um esquecido functor U da categoria de mônadas, onde as flechas são mônadas homomorfismos, até uma categoria de endofuncionadores, onde as flechas são transformações naturais, e procuramos um functor que fica ao lado para isso.

Então, como isso se relaciona com a noção de mônada livre, como costuma ser usada?

Saber que algo é uma mônada livre Free f, diz a você que fornecer uma homônima de mônada Free f -> mé a mesma coisa (isomórfica a) que fornecer uma transformação natural (de um homomorfismo de functor) f -> m. Lembre-se de que F a -> bdeve ser isomórfico para a -> U bque F seja deixado adjacente a U. U aqui mapeou mônadas para functores.

F é pelo menos isomorfo ao Freetipo que eu uso no meu freepacote sobre hackage.

Também podemos construí-lo em analogia mais rigorosa ao código acima para a lista livre, definindo

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

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

Comonadas Cofree

Podemos construir algo semelhante, observando o anexo correto de um functor esquecido, assumindo que ele existe. Um functor co-livre é simplesmente / diretamente adjunto / a um functor esquecido e, por simetria, saber que algo é uma comonada livre é o mesmo que saber que dar homomorfismo a uma comonada w -> Cofree fé o mesmo que dar uma transformação natural w -> f.

Edward KMETT
fonte
12
@PauloScardine, isso não é nada que você tem que se preocupar com. Minha pergunta veio do interesse de entender alguma estrutura avançada de dados e talvez ter uma idéia do que há de mais avançado no desenvolvimento de Haskell no momento - não é de forma alguma necessário ou representativo do que realmente está escrevendo Haskell até agora. (E, lembrando, fica melhor depois que você passa pelo estágio de aprendizado de IO novamente) #
David
8
@PauloScardine Você não precisa da resposta acima para programar produtivamente em Haskell, mesmo com mônadas gratuitas. De fato, eu não recomendaria atacar a mônada livre dessa maneira para alguém que não tenha formação em teoria de categorias. Existem várias maneiras de falar sobre isso de uma perspectiva operacional e saber como usá-lo sem mergulhar na teoria das categorias. No entanto, é impossível para mim responder à pergunta de onde eles vêm sem mergulhar na teoria. Construções gratuitas são uma ferramenta poderosa na teoria das categorias, mas você não precisa desse histórico para usá-las.
Edward KMETT 13/11/2012
18
@PauloScardine: Você não precisa exatamente de cálculo para utilizar o Haskell de maneira eficaz e até entender o que está fazendo. É um pouco estranho reclamar que "essa linguagem é matemática" quando a matemática é apenas uma bondade extra que você pode usar para se divertir e lucrar. Você não consegue essas coisas nas línguas mais imperativas. Por que você reclamaria de extras? Você pode optar por NÃO raciocinar matematicamente e abordá-lo como faria com qualquer outro novo idioma.
Sarah
3
@ Sarah: Ainda estou para ver uma documentação ou uma conversa no IRC sobre haskell que não é pesada em teoria de computadores e em termos de cálculo lambda.
Paulo Scardine
11
@PauloScardine, isso está perdendo um pouco o AT, mas na defesa de Haskell: coisas técnicas semelhantes se aplicam a todas as outras linguagens de programação, só que Haskell tem uma compilação tão agradável que as pessoas podem realmente gostar de falar sobre elas. Por que / como X é uma mônada é interessante para muitas pessoas, discussões sobre o padrão de ponto flutuante IEEE não são; ambos os casos não importam para a maioria das pessoas, porque eles podem apenas usar os resultados.
David
72

A Mônada Livre (estrutura de dados) é para a Mônada (classe) como a Lista (estrutura de dados) para a Monóide (classe): é a implementação trivial, onde você pode decidir depois como o conteúdo será combinado.


Você provavelmente sabe o que é uma Mônada e que cada Mônada precisa de uma implementação específica (cumprindo a lei) de fmap+ join+ returnou bind+ return.

Vamos supor que você tenha um Functor (uma implementação de fmap), mas o restante depende dos valores e escolhas feitos no tempo de execução, o que significa que você deseja poder usar as propriedades da Mônada, mas depois escolher as funções da Mônada.

Isso pode ser feito usando a Mônada Livre (estrutura de dados), que agrupa o Functor (tipo) de tal maneira que joinseja mais um empilhamento desses functores do que uma redução.

O real returne joinvocê deseja usar agora podem ser dados como parâmetros para a função de redução foldFree:

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

Para explicar os tipos, podemos substituir Functor fpor Monad me bpor (m a):

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)
comonad
fonte
8
Essa resposta me deu a impressão de que eu entendo para que eles podem até ser úteis.
David
59

Uma mônada livre de Haskell é uma lista de functores. Comparar:

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

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

Pureé análogo Nile Freeé análogo a Cons. Uma mônada livre armazena uma lista de functores em vez de uma lista de valores. Tecnicamente, você pode implementar mônadas gratuitas usando um tipo de dados diferente, mas qualquer implementação deve ser isomórfica à mencionada acima.

Você usa mônadas gratuitas sempre que precisar de uma árvore de sintaxe abstrata. O functor base da mônada livre é o formato de cada etapa da árvore de sintaxe.

Meu post , que alguém já vinculou, fornece vários exemplos de como criar árvores de sintaxe abstrata com mônadas gratuitas

Gabriel Gonzalez
fonte
6
Eu sei que você estava apenas fazendo uma analogia em vez de fazer uma definição, mas uma mônada livre certamente não é análoga a uma lista de functores em nenhum sentido. É muito mais perto de uma árvore de functores.
Tom Ellis
6
Eu mantenho minha terminologia. Por exemplo, usando meu pacote index-core, você pode definir "compreensões livres de mônada", que se comportam exatamente como a mônada de lista, exceto que você liga functors em vez de valores. Uma mônada livre é uma lista de functores no sentido de que, se você traduzir todos os conceitos de Haskell na categoria de functores, as listas se tornarão mônadas livres. Uma verdadeira árvore de functores então se torna algo completamente diferente.
Gabriel Gonzalez
4
Você está certo de que a mônada é a categorização, em certo sentido, do conceito de monóide; portanto, as mônadas livres são análogas às monóides livres, ou seja, listas. Nessa medida, você certamente está correto. No entanto, a estrutura de um valor de uma mônada livre não é uma lista. É uma árvore, como detalho abaixo .
Tom Ellis
2
@ TomEllis Tecnicamente, será apenas uma árvore se o seu functor base for um produto de produto. Quando você tem um functor de soma como o functor de base, ele se parece mais com uma máquina de empilhar.
Gabriel Gonzalez
21

Eu acho que um exemplo concreto simples ajudará. Suponha que tenhamos um functor

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

com o óbvio fmap. Então Free F aé o tipo de árvores cujas folhas têm tipo ae cujos nós são marcados com One, Two, Two'e Three. One-nodes têm um filho, Two- e Two'-nodes têm dois filhos e Three-nodes têm três e também são marcados com um Int.

Free Fé uma mônada. returnmapeia xpara a árvore que é apenas uma folha com valor x. t >>= folha para cada uma das folhas e as substitui por árvores. Quando a folha tem valor, yela substitui a folha pela árvore f y.

Um diagrama torna isso mais claro, mas eu não tenho as facilidades para desenhar facilmente um!

Tom Ellis
fonte
14
O que vocês estão dizendo é que a mônada livre assume a forma do próprio functor. Portanto, se o functor for do tipo árvore (produtos), a mônada livre é do tipo árvore; se for do tipo lista (somas), a mônada livre é do tipo lista; se for semelhante à função, a mônada livre é semelhante à função; etc Isso faz sentido para mim. Assim, como em um monóide livre, você continua tratando cada aplicativo do mappend como criando um elemento completamente novo; na mônada livre, você trata todas as aplicações do functor como um elemento completamente novo.
Bartosz Milewski
4
Mesmo se o functor for um "sum functor", a mônada livre resultante ainda será semelhante a uma árvore. Você acaba com mais de um tipo de nó na sua árvore: um para cada componente da sua soma. Se o seu "soma functor" é X -> 1 + X, na verdade você recebe uma lista, que é apenas um tipo de árvore degenerada.
Tom Ellis