O que é uma mônada?


1415

Tendo examinado brevemente Haskell recentemente, qual seria uma explicação breve, sucinta e prática sobre o que é essencialmente uma mônada?

Descobri que a maioria das explicações que encontrei é bastante inacessível e carece de detalhes práticos.


12
Eric Lippert escreveu uma resposta para estas perguntas ( stackoverflow.com/questions/2704652/… ), que é devido a alguns problemas em uma página separada.
usar o seguinte comando

70
Aqui está uma nova introdução usando javascript - achei muito legível.
Benjol 31/03



2
Uma mônada é uma matriz de funções com operações auxiliares. Veja esta resposta
cibercitizen1

Respostas:


1060

Primeiro: o termo mônada é um pouco vazio se você não é um matemático. Um termo alternativo é construtor de computação, que é um pouco mais descritivo do que eles realmente são úteis.

Você pede exemplos práticos:

Exemplo 1: Compreensão da lista :

[x*2 | x<-[1..10], odd x]

Essa expressão retorna as duplas de todos os números ímpares no intervalo de 1 a 10. Muito útil!

Acontece que este é realmente apenas um açúcar sintático para algumas operações dentro da mônada da Lista. A mesma compreensão da lista pode ser escrita como:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

Ou até:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Exemplo 2: Entrada / Saída :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

Ambos os exemplos usam mônadas, construtores de computação AKA. O tema comum é que a mônada encadeia operações de alguma maneira específica e útil. Na compreensão da lista, as operações são encadeadas de modo que, se uma operação retorna uma lista, as seguintes operações são executadas em todos os itens da lista. A mônada de E / S, por outro lado, executa as operações sequencialmente, mas transmite uma "variável oculta", que representa "o estado do mundo", o que nos permite escrever o código de E / S de uma maneira pura e funcional.

Acontece que o padrão de operações de encadeamento é bastante útil e é usado para muitas coisas diferentes em Haskell.

Outro exemplo são as exceções: usando a Errormônada, as operações são encadeadas de forma que sejam executadas seqüencialmente, exceto se um erro for gerado; nesse caso, o restante da cadeia será abandonado.

Tanto a sintaxe de compreensão da lista quanto a doação são açúcar sintático para operações de encadeamento usando o >>=operador. Uma mônada é basicamente apenas um tipo que suporta o >>=operador.

Exemplo 3: Um analisador

Este é um analisador muito simples que analisa uma string ou um número entre aspas:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

As operações char, digitetc. são bastante simples. Eles combinam ou não. A mágica é a mônada que gerencia o fluxo de controle: as operações são executadas seqüencialmente até que uma correspondência falhe; nesse caso, a mônada retorna para a última <|>e tenta a próxima opção. Novamente, uma maneira de encadear operações com alguma semântica adicional e útil.

Exemplo 4: Programação assíncrona

Os exemplos acima estão em Haskell, mas o F # também suporta mônadas. Este exemplo é roubado de Don Syme :

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

Este método busca uma página da web. A linha de perfuração é o uso de GetResponseAsync- na verdade aguarda a resposta em um thread separado, enquanto o thread principal retorna da função. As últimas três linhas são executadas no encadeamento gerado quando a resposta é recebida.

Na maioria dos outros idiomas, você teria que criar explicitamente uma função separada para as linhas que lidam com a resposta. A asyncmônada é capaz de "dividir" o bloco por conta própria e adiar a execução da segunda metade. (A async {}sintaxe indica que o fluxo de controle no bloco é definido pela asyncmônada.)

Como eles trabalham

Então, como uma mônada pode fazer tudo isso? O que realmente acontece em um bloco de tarefas (ou em uma expressão de cálculo, como são chamados em F #), é que todas as operações (basicamente todas as linhas) são agrupadas em uma função anônima separada. Essas funções são então combinadas usando o bindoperador (escrito >>=em Haskell). Como a bindoperação combina funções, ela pode executá-las como achar necessário: sequencialmente, várias vezes, ao contrário, descarta algumas, executa algumas em um encadeamento separado quando lhe apetecer e assim por diante.

Como exemplo, esta é a versão expandida do código IO do exemplo 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

Isso é mais feio, mas também é mais óbvio o que realmente está acontecendo. O >>=operador é o ingrediente mágico: ele pega um valor (no lado esquerdo) e o combina com uma função (no lado direito), para produzir um novo valor. Esse novo valor é então utilizado pelo próximo >>=operador e novamente combinado com uma função para produzir um novo valor. >>=pode ser visto como um mini-avaliador.

Observe que >>=está sobrecarregado para tipos diferentes, portanto, cada mônada tem sua própria implementação >>=. (Todas as operações na cadeia precisam ser do tipo da mesma mônada, caso contrário, o >>=operador não funcionará.)

A implementação mais simples possível >>=apenas pega o valor à esquerda e aplica-o à função à direita e retorna o resultado, mas, como dito anteriormente, o que torna todo o padrão útil é quando há algo extra acontecendo na implementação da mônada de >>=.

Há alguma inteligência adicional em como os valores são passados ​​de uma operação para a seguinte, mas isso requer uma explicação mais profunda do sistema do tipo Haskell.

Resumindo

Em termos de Haskell, uma mônada é um tipo parametrizado que é uma instância da classe de tipo Mônada, que define >>=junto com alguns outros operadores. Em termos leigos, uma mônada é apenas um tipo para o qual a >>=operação está definida.

Por si >>=só, é apenas uma maneira complicada de encadear funções, mas com a presença da notação que oculta o "encanamento", as operações monádicas acabam sendo uma abstração muito agradável e útil, útil em muitos lugares da linguagem e útil para criar seus próprios mini-idiomas no idioma.

Por que as mônadas são difíceis?

Para muitos aprendizes de Haskell, as mônadas são um obstáculo que atingem como uma parede de tijolos. Não é que as mônadas sejam complexas, mas a implementação depende de muitos outros recursos avançados do Haskell, como tipos parametrizados, classes de tipos e assim por diante. O problema é que o Haskell I / O é baseado em mônadas, e é provavelmente uma das primeiras coisas que você deseja entender ao aprender um novo idioma - afinal, não é muito divertido criar programas que não produzem nenhum resultado. Não tenho solução imediata para esse problema de galinha e ovo, exceto tratar a E / S como "mágica acontece aqui" até que você tenha experiência suficiente com outras partes da linguagem. Desculpa.

Excelente blog sobre mônadas: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


66
Como alguém que teve muitos problemas para entender mônadas, posso dizer que essa resposta ajudou .. um pouco. No entanto, ainda existem algumas coisas que eu não entendo. De que maneira a compreensão da lista é uma mônada? Existe uma forma expandida desse exemplo? Outra coisa que realmente me incomoda com a maioria das explicações sobre mônadas, incluindo esta: é que elas continuam misturando "o que é uma mônada?" com "para que serve uma mônada?" e "Como uma mônada é implementada?". você pulou no tubarão quando escreveu "Uma mônada é basicamente apenas um tipo que suporta o operador >> =". Que só tinha me ...
Breton

83
Também não concordo com sua conclusão sobre por que as mônadas são difíceis. Se as próprias mônadas não forem complexas, você poderá explicar o que elas são sem um monte de bagagem. Não quero saber sobre a implementação quando fizer a pergunta "O que é uma mônada", quero saber o que a coceira deve significar. Até agora, parece que a resposta é "Como os autores do haskell são sadomasoquistas e decidiram que você deveria fazer algo estupidamente complexo para realizar coisas simples, então você precisa aprender mônadas a usar o haskell, não porque elas sejam úteis de alguma maneira. "
Breton

70
Mas .. isso não pode estar certo, pode? Penso que as mônadas são difíceis porque ninguém parece descobrir como explicá-las sem se envolver em detalhes confusos da implementação. Quero dizer .. o que é um ônibus escolar? É uma plataforma de metal com um dispositivo na frente que consome um produto de petróleo refinado para acionar em um ciclo alguns pistões metálicos, que por sua vez giram um eixo de manivela preso a algumas engrenagens que acionam algumas rodas. As rodas inflaram sacos de borracha ao redor deles, que interagem com uma superfície de asfalto para fazer com que uma coleção de assentos avance. Os assentos avançam porque ...
Breton

130
Eu li tudo isso e ainda não sei o que é uma mônada, além do fato de que é algo que os programadores de Haskell não entendem bem o suficiente para explicar. Os exemplos não ajudam muito, dado que tudo isso pode ser feito sem mônadas, e essa resposta não deixa claro como as mônadas as tornam mais fáceis, apenas mais confusas. A parte dessa resposta que quase chegou a ser útil foi a remoção do açúcar sintático do exemplo # 2. Eu digo que chegou perto porque, além da primeira linha, a expansão não tem nenhuma semelhança real com a original.
Laurence Gonsalves

81
Outro problema que parece endêmico às explicações das mônadas é que está escrito em Haskell. Não estou dizendo que Haskell é uma linguagem ruim - estou dizendo que é uma linguagem ruim para explicar mônadas. Se eu conhecesse Haskell, já entenderia mônadas. Portanto, se você quiser explicar mônadas, comece usando uma linguagem que as pessoas que não conhecem mônadas têm maior probabilidade de entender. Se você precisar usar o Haskell, não use o açúcar sintático - use o subconjunto menor e mais simples do idioma que puder e não assuma o entendimento do Haskell IO.
Laurence Gonsalves

712

Explicar "o que é uma mônada" é como dizer "o que é um número?" Usamos números o tempo todo. Mas imagine que você conheceu alguém que não sabia nada sobre números. Como diabos você explicaria o que são números? E como você começaria a descrever por que isso pode ser útil?

O que é uma mônada? A resposta curta: é uma maneira específica de encadear operações.

Em essência, você está escrevendo etapas de execução e vinculando-as à "função de ligação". (Em Haskell, ele é chamado >>=.) Você pode gravar as chamadas para o operador de ligação, ou pode usar a sintaxe sugar, que faz com que o compilador insira essas chamadas de função para você. Mas, de qualquer maneira, cada etapa é separada por uma chamada para essa função de ligação.

Portanto, a função de ligação é como um ponto e vírgula; separa as etapas em um processo. O trabalho da função de ligação é pegar a saída da etapa anterior e alimentá-la na próxima etapa.

Isso não parece muito difícil, certo? Mas há mais de um tipo de mônada. Por quê? Quão?

Bem, a função de ligação pode apenas pegar o resultado de uma etapa e alimentá-lo para a próxima etapa. Mas se isso é "tudo" a mônada faz ... isso realmente não é muito útil. E isso é importante para entender: toda mônada útil faz outra coisa além de ser apenas uma mônada. Toda mônada útil tem um "poder especial", o que a torna única.

(Uma mônada que não faz nada de especial é chamada de "mônada de identidade". Mais ou menos como a função de identidade, isso soa como uma coisa totalmente sem sentido, mas acaba não sendo ... Mas isso é outra história ™.)

Basicamente, cada mônada tem sua própria implementação da função de ligação. E você pode escrever uma função de ligação, de modo que faça coisas interessantes entre as etapas de execução. Por exemplo:

  • Se cada etapa retornar um indicador de êxito / falha, você poderá executar a ligação na próxima etapa apenas se a anterior tiver sido bem-sucedida. Dessa forma, uma etapa com falha aborta toda a sequência "automaticamente", sem nenhum teste condicional de você. (A Mônada de Falha .)

  • Estendendo essa idéia, você pode implementar "exceções". (A Mônada de erro ou Mônaco de exceção ). Como você está definindo elas mesmas, e não como um recurso de idioma, é possível definir como elas funcionam. (Por exemplo, talvez você queira ignorar as duas primeiras exceções e abortar apenas quando uma terceira exceção for lançada.)

  • Você pode fazer com que cada etapa retorne vários resultados e faça com que a função de vinculação faça um loop sobre eles, alimentando cada uma delas na próxima etapa. Dessa forma, você não precisa continuar escrevendo loops em todo o lugar ao lidar com vários resultados. A função de ligação "automaticamente" faz tudo isso para você. (A lista Mônada .)

  • Além de passar um "resultado" de uma etapa para outra, você pode fazer com que a função de ligação passe dados extras também. Esses dados agora não aparecem no seu código-fonte, mas você ainda pode acessá-los de qualquer lugar, sem precisar passá-los manualmente para todas as funções. (O leitor Mônada .)

  • Você pode fazer isso para que os "dados extras" possam ser substituídos. Isso permite simular atualizações destrutivas , sem realmente fazer atualizações destrutivas. (A Mônada do Estado e seu primo, a Mônada do Escritor .)

  • Como você está apenas simulando atualizações destrutivas, pode fazer coisas triviais que seriam impossíveis com atualizações destrutivas reais . Por exemplo, você pode desfazer a última atualização ou reverter para uma versão mais antiga .

  • Você pode fazer uma mônada onde os cálculos podem ser pausados , para que você possa pausar seu programa, entrar e mexer nos dados internos do estado e depois retomar.

  • Você pode implementar "continuações" como uma mônada. Isso permite que você quebre a mente das pessoas!

Tudo isso e muito mais é possível com mônadas. Obviamente, tudo isso também é perfeitamente possível sem mônadas. É drasticamente mais fácil usar mônadas.


13
Agradeço sua resposta - especialmente a concessão final de que tudo isso também é possível sem mônadas. Um ponto a ser destacado é que é mais fácil com mônadas, mas geralmente não é tão eficiente quanto fazê-lo sem elas. Depois que você precisa envolver transformadores, as camadas extras de chamadas de função (e objetos de função criados) têm um custo difícil de ver e controlar, tornado invisível por uma sintaxe inteligente.
seh

1
Pelo menos em Haskell, a maior parte das despesas gerais das mônadas é removida pelo otimizador. Portanto, o único "custo" real é o poder cerebral necessário. (Isso não é insignificante se "manutenção" é algo que você gosta.) Mas, geralmente, as mônadas tornam as coisas mais fáceis , não mais difíceis. (Caso contrário, por que se preocupar?)
MathematicalOrchid

Não tenho certeza se Haskell suporta ou não isso, mas matematicamente você pode definir uma mônada em termos de >> = e retornar ou ingressar e ap. >> = e return são o que torna as mônadas praticamente úteis, mas se juntam e fornecem uma compreensão mais intuitiva do que é uma mônada.
list

15
Vindo de um histórico de programação não-matemática e não-funcional, essa resposta fez mais sentido para mim.
precisa saber é o seguinte

10
Esta é a primeira resposta que realmente me deu uma idéia do que diabos é uma mônada. Obrigado por encontrar uma maneira de explicar isso!
robotmay

186

Na verdade, ao contrário do entendimento comum das mônadas, elas nada têm a ver com o estado. Mônadas são simplesmente uma maneira de embrulhar as coisas e fornecer métodos para executar operações no material embrulhado sem desembrulhá-lo.

Por exemplo, você pode criar um tipo para quebrar outro, em Haskell:

data Wrapped a = Wrap a

Para embrulhar coisas que definimos

return :: a -> Wrapped a
return x = Wrap x

Para executar operações sem desembrulhar, digamos que você tenha uma função f :: a -> b, você pode fazer isso para elevar essa função para agir com base em valores agrupados:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

Isso é tudo o que há para entender. No entanto, verifica-se que há uma função mais geral para fazer esse levantamento , que é bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindpode fazer um pouco mais do que fmap, mas não vice-versa. Na verdade, só fmappode ser definido em termos de binde return. Portanto, ao definir uma mônada, você indica o tipo (aqui estava Wrapped a) e depois diz como as operações returne bindas funções funcionam.

O legal é que isso acaba sendo um padrão tão geral que aparece em todo o lugar, encapsular o estado de maneira pura é apenas um deles.

Para um bom artigo sobre como as mônadas podem ser usadas para introduzir dependências funcionais e, assim, controlar a ordem de avaliação, como é usada na mônada de IO da Haskell, consulte IO Inside .

Quanto à compreensão das mônadas, não se preocupe muito com isso. Leia sobre eles o que achar interessante e não se preocupe se não entender imediatamente. Depois, basta mergulhar em um idioma como Haskell. As mônadas são uma dessas coisas em que a compreensão entra em seu cérebro pela prática; um dia você percebe de repente que as entende.


-> é uma aplicação de função de espelhamento associativa à direita, associativa à esquerda; portanto, deixar os parênteses de fora não faz diferença aqui.
Matthias Benkard 11/10/08

1
Não acho que seja uma explicação muito boa. Mônadas são simplesmente uma maneira? ok, para que lado? Por que não encapsularia usando uma classe em vez de uma mônada?
Breton

4
@ mb21: Caso você esteja apenas apontando que existem colchetes demais, observe que a-> b-> c é realmente apenas a abreviação de a -> (b-> c). Escrever este exemplo em particular como (a -> b) -> (Ta -> Tb) é apenas o acréscimo de caracteres desnecessários, mas é moralmente "a coisa certa a se fazer", pois enfatiza que o fmap mapeia uma função do tipo a -> b para uma função do tipo Ta -> Tb. E, originalmente, é isso que os functores fazem na teoria das categorias e é daí que as mônadas vêm.
precisa

1
Esta resposta é enganosa. Algumas mônadas não possuem um "invólucro", como funções de um valor fixo.

1
As mônadas do @DanMandel são padrões de design que fornecem seu próprio invólucro de tipo de dados. Mônadas são projetadas de forma a abstrair o código padrão. Então, como você chama uma Mônada em seu código, ela faz coisas nos bastidores que você não quer se preocupar. Pense em Nullable <T> ou IEnumerable <T>, o que eles fazem nos bastidores? Isso é Mônada.
sksallaj 16/01

168

Mas, você poderia ter inventado as mônadas!

sigfpe diz:

Mas tudo isso introduz mônadas como algo esotérico que precisa de explicação. Mas o que quero argumentar é que eles não são esotéricos. De fato, diante de vários problemas na programação funcional, você seria levado, inexoravelmente, a certas soluções, exemplos de mônadas. Na verdade, espero que você os invente agora, se ainda não o fez. É então um pequeno passo para perceber que todas essas soluções são de fato a mesma solução disfarçada. E depois de ler isso, você estará em melhor posição para entender outros documentos nas mônadas, porque reconhecerá tudo o que vê como algo que já inventou.

Muitos dos problemas que as mônadas tentam resolver estão relacionados à questão dos efeitos colaterais. Então, vamos começar com eles. (Observe que as mônadas permitem fazer mais do que lidar com efeitos colaterais, em particular muitos tipos de objetos de contêiner podem ser vistos como mônadas. Algumas das introduções a mônadas acham difícil conciliar esses dois usos diferentes das mônadas e concentrar-se em apenas um ou o outro.)

Em uma linguagem de programação imperativa como o C ++, funções não se comportam como as funções da matemática. Por exemplo, suponha que tenhamos uma função C ++ que pega um único argumento de ponto flutuante e retorna um resultado de ponto flutuante. Superficialmente, pode parecer um pouco uma função matemática que mapeia reais para reais, mas uma função C ++ pode fazer mais do que apenas retornar um número que depende de seus argumentos. Ele pode ler e gravar os valores de variáveis ​​globais, além de gravar a saída na tela e receber informações do usuário. Em uma linguagem funcional pura, no entanto, uma função só pode ler o que é fornecido em seus argumentos e a única maneira de afetar o mundo é através dos valores que ela retorna.


9
… Melhor maneira, não apenas na internet, mas em qualquer lugar. (O artigo original de Wadler, Mônadas para programação funcional, que mencionei na minha resposta abaixo, também é bom.) Nenhum dos zilhões de tutoriais por analogia chega perto.
ShreevatsaR

13
Esta tradução em JavaScript da postagem de Sigfpe é a nova melhor maneira de aprender mônadas, para pessoas que ainda não se divertem com o avançado Haskell!
Sam Watkins

1
Foi assim que aprendi o que é uma mônada. Conduzir o leitor pelo processo de invenção de um conceito geralmente é a melhor maneira de ensiná-lo.
318 Jordan

No entanto, uma função que aceite o objeto de tela como argumento e retorne sua cópia com o texto modificado seria pura.
Dmitri Zaitsev

87

Uma mônada é um tipo de dados que possui duas operações: >>=(aka bind) e return(aka unit). returnpega um valor arbitrário e cria uma instância da mônada com ele. >>=pega uma instância da mônada e mapeia uma função sobre ela. (Você já pode ver que uma mônada é um tipo estranho de tipo de dados, pois na maioria das linguagens de programação não era possível escrever uma função que pega um valor arbitrário e cria um tipo a partir dela. As mônadas usam um tipo de polimorfismo paramétrico .)

Na notação Haskell, a interface monad é escrita

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

Essas operações devem obedecer a certas "leis", mas isso não é muito importante: as "leis" apenas codificam a maneira como as implementações sensíveis das operações devem se comportar (basicamente, isso >>=e returndevem concordar sobre como os valores são transformados em instâncias mônadas e isso >>=é associativo).

As mônadas não são apenas sobre estado e E / S: elas abstraem um padrão comum de computação que inclui trabalhar com estado, E / S, exceções e não-determinismo. Provavelmente, as mônadas mais simples de entender são listas e tipos de opções:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

onde []e :são os construtores da lista, ++é o operador de concatenação e Juste Nothingsão os Maybeconstrutores. Ambas as mônadas encapsulam padrões comuns e úteis de computação em seus respectivos tipos de dados (observe que nenhum deles tem nada a ver com efeitos colaterais ou E / S).

Você realmente precisa escrever um código Haskell não trivial para apreciar o que são as mônadas e por que são úteis.


O que exatamente você quer dizer com "mapeia uma função sobre ela"?
Casebash

Casebash, estou sendo deliberadamente informal na introdução. Veja os exemplos perto do fim para ter uma idéia do que "mapear uma função" implica.
Chris Conway

3
Mônada não é um tipo de dados. É uma regra de composição de funções: stackoverflow.com/a/37345315/1614973 #
Dmitri Zaitsev 20/16

@DmitriZaitsev está certo, os Monads realmente fornecem seu próprio tipo de dados, os Monads não são
sksallaj 16/01

78

Você deve primeiro entender o que é um functor. Antes disso, entenda as funções de ordem superior.

Uma função de ordem superior é simplesmente uma função que aceita uma função como argumento.

Um functor é qualquer construção de tipo Tpara a qual exista uma função de ordem superior, chame-a map, que transforma uma função do tipo a -> b(dados dois tipos ae b) em uma função T a -> T b. Essa mapfunção também deve obedecer às leis de identidade e composição, de modo que as seguintes expressões retornem verdadeiras para todos pe q(notação de Haskell):

map id = id
map (p . q) = map p . map q

Por exemplo, um construtor de tipo chamado Listé um functor se for equipado com uma função do tipo (a -> b) -> List a -> List bque obedece às leis acima. A única implementação prática é óbvia. A List a -> List bfunção resultante itera sobre a lista fornecida, chamando a (a -> b)função para cada elemento e retorna a lista dos resultados.

A mônada é essencialmente apenas um functor Tcom dois métodos extra, join, do tipo T (T a) -> T a, e unit(às vezes chamado return, forkou pure) do tipo a -> T a. Para listas em Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Por que isso é útil? Porque você pode, por exemplo, mapsobre uma lista com uma função que retorna uma lista. Joinpega a lista resultante de listas e as concatena. Listé uma mônada porque isso é possível.

Você pode escrever uma função que sim map, então join. Essa função é chamada bind, ou flatMap, ou (>>=), ou (=<<). Normalmente, é assim que uma instância de mônada é fornecida no Haskell.

Uma mônada deve satisfazer certas leis, a saber, que joindevem ser associativas. Isso significa que, se você tiver um valor xdo tipo [[[a]]], join (join x)deverá ser igual join (map join x). E puredeve ser uma identidade para jointal join (pure x) == x.


3
pequena adição à definição da 'função de ordem superior': eles podem assumir as funções OR RETURN. É por isso que eles são 'superiores', porque fazem coisas sozinhos.
Kevin ganhou

9
Por essa definição, a adição é uma função de ordem superior. Ele pega um número e retorna uma função que adiciona esse número a outro. Portanto, não, funções de ordem superior são estritamente funções cujo domínio consiste em funções.
Apocalisp 30/01

O vídeo ' Brian Beckman: Não tema a mônada ' segue esta mesma linha de lógica.
Icc97 10/10

48

[Isenção de responsabilidade: ainda estou tentando gritar completamente as mônadas. A seguir, é exatamente o que eu entendi até agora. Se estiver errado, espero que alguém com conhecimento me ligue no tapete.]

Arnar escreveu:

Mônadas são simplesmente uma maneira de embrulhar as coisas e fornecer métodos para executar operações no material embrulhado sem desembrulhá-lo.

É exatamente isso. A ideia é a seguinte:

  1. Você pega algum tipo de valor e o envolve com algumas informações adicionais. Assim como o valor é de um determinado tipo (por exemplo, um número inteiro ou uma string), as informações adicionais são de um determinado tipo.

    Por exemplo, essa informação extra pode ser um Maybeou um IO.

  2. Então você tem alguns operadores que permitem operar os dados agrupados enquanto carregam essas informações adicionais. Esses operadores usam as informações adicionais para decidir como alterar o comportamento da operação no valor agrupado.

    Por exemplo, a Maybe Intpode ser um Just Intou Nothing. Agora, se você adicionar um Maybe Inta a Maybe Int, o operador verificará se ambos estão Just Intdentro de si e, se houver, desembrulhará os Ints, passará a eles o operador de adição e reorganizará o resultado Intem um novo Just Int(que é válido Maybe Int) e, portanto, retorne a Maybe Int. Mas se um deles fosse Nothinginterno, esse operador retornaria imediatamente Nothing, o que novamente é válido Maybe Int. Dessa forma, você pode fingir que seus Maybe Intnúmeros são apenas normais e executar matemática regularmente com eles. Se você conseguir um Nothing, suas equações ainda produzirão o resultado certo - sem que você precise fazer verificações de lixo em Nothingtodos os lugares .

Mas o exemplo é exatamente o que acontece Maybe. Se a informação extra fosse um IO, então esse operador especial definido para IOs seria chamado e poderia fazer algo totalmente diferente antes de realizar a adição. (OK, somar dois IO Ints provavelmente não faz sentido - ainda não tenho certeza.) (Além disso, se você prestou atenção ao Maybeexemplo, notou que "agrupar um valor com coisas extras" nem sempre é correto. Mas é difícil para ser exato, correto e preciso sem ser inescrutável.)

Basicamente, "mônada" significa aproximadamente "padrão" . Mas, em vez de um livro cheio de padrões explicados informalmente e nomeados especificamente, agora você tem uma construção de linguagem - sintaxe e tudo - que permite declarar novos padrões como itens de seu programa . (A imprecisão aqui é que todos os padrões precisam seguir uma forma específica, de modo que uma mônada não é tão genérica quanto um padrão. Mas acho que esse é o termo mais próximo que a maioria das pessoas conhece e entende.)

E é por isso que as pessoas acham as mônadas tão confusas: porque são um conceito tão genérico. Perguntar o que faz de algo uma mônada é igualmente vago, como perguntar o que faz de algo um padrão.

Mas pense nas implicações de ter suporte sintático na linguagem para a idéia de um padrão: em vez de ter que ler o livro da Gangue dos Quatro e memorizar a construção de um padrão específico, basta escrever um código que implemente esse padrão de forma agnóstica, maneira genérica uma vez e então está pronto! Você pode reutilizar esse padrão, como Visitor ou Strategy ou Façade ou qualquer outra coisa, apenas decorando as operações em seu código com ele, sem ter que reimplementá-lo repetidamente!

É por isso que as pessoas que entendem as mônadas as acham tão úteis : não é um conceito de torre de marfim que os esnobes intelectuais se orgulham de entender (OK, isso também é claro, eles), mas na verdade torna o código mais simples.


12
Às vezes, uma explicação de um "aluno" (como você) é mais relevante para outro aluno do que uma explicação de um especialista. Alunos pensar :) iguais
Adrian

O que faz de algo uma mônada é a existência de uma função com o tipo M (M a) -> M a. O fato de você poder transformar isso em um tipo M a -> (a -> M b) -> M bé o que os torna úteis.
Lista Jeremy

"mônada" significa aproximadamente "padrão" ... não.
Obrigado

44

Depois de muito esforço, acho que finalmente entendi a mônada. Depois de reler minha longa crítica à resposta predominantemente votada, apresentarei esta explicação.

Há três perguntas que precisam ser respondidas para entender as mônadas:

  1. Por que você precisa de uma mônada?
  2. O que é uma mônada?
  3. Como uma mônada é implementada?

Como observei em meus comentários originais, muitas explicações sobre mônadas são abordadas na questão número 3, sem e antes de realmente cobrir adequadamente a questão 2 ou a questão 1.

Por que você precisa de uma mônada?

Linguagens funcionais puras como Haskell são diferentes das linguagens imperativas como C ou Java, pois um programa funcional puro não é necessariamente executado em uma ordem específica, uma etapa de cada vez. Um programa Haskell é mais parecido com uma função matemática, na qual você pode resolver a "equação" em qualquer número de possíveis ordens. Isso confere uma série de benefícios, entre os quais se elimina a possibilidade de certos tipos de bugs, particularmente aqueles relacionados a coisas como "estado".

No entanto, existem certos problemas que não são tão simples de resolver com esse estilo de programação. Algumas coisas, como a programação do console e a E / S de arquivo, precisam que as coisas aconteçam em uma ordem específica ou que mantenham o estado. Uma maneira de lidar com esse problema é criar um tipo de objeto que represente o estado de uma computação e uma série de funções que tomam um objeto de estado como entrada e retornam um novo objeto de estado modificado.

Então, vamos criar um valor hipotético de "estado", que representa o estado de uma tela do console. exatamente como esse valor é construído não é importante, mas digamos que seja uma matriz de caracteres ascii de comprimento de byte que represente o que está atualmente visível na tela e uma matriz que represente a última linha de entrada inserida pelo usuário no pseudocódigo. Definimos algumas funções que assumem o estado do console, modificam-no e retornam um novo estado do console.

consolestate MyConsole = new consolestate;

Então, para fazer a programação do console, mas de uma maneira funcional pura, você precisaria aninhar muitas chamadas de função dentro de cada um.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

Programar dessa maneira mantém o estilo funcional "puro", enquanto força as alterações no console para acontecer em uma ordem específica. Mas, provavelmente, queremos fazer mais do que apenas algumas operações por vez, como no exemplo acima. As funções de aninhamento dessa maneira começarão a se tornar desajeitadas. O que queremos é um código que faça essencialmente a mesma coisa que acima, mas que seja escrito um pouco mais como este:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

Esta seria realmente uma maneira mais conveniente de escrevê-lo. Como fazemos isso?

O que é uma mônada?

Depois de ter um tipo (como consolestate) que você define, juntamente com várias funções projetadas especificamente para operar nesse tipo, você pode transformar todo o pacote dessas coisas em uma "mônada", definindo um operador como :(bind) que automaticamente feeds retornam valores à esquerda, para parâmetros de função à direita, e um liftoperador que transforma funções normais, em funções que funcionam com esse tipo específico de operador de ligação.

Como uma mônada é implementada?

Veja outras respostas, que parecem bastante livres para entrar nos detalhes disso.


Sequenciar não é o único motivo para definir uma mônada. Uma mônada é apenas qualquer função que tenha ligação e retorno. Vincular e retornar fornecem seqüenciamento. Mas eles dão outras coisas também. Além disso, observe que sua linguagem imperativa favorita é efetivamente uma mônada sofisticada de IO com classes OO. Facilitar a definição de mônadas significa que é fácil usar o padrão de intérprete - defina um dsl como mônada e interprete-o!
Dez


38

Depois de responder a esta pergunta há alguns anos, acredito que posso melhorar e simplificar essa resposta com ...

Uma mônada é uma técnica de composição de funções que externaliza o tratamento para alguns cenários de entrada usando uma função de composição bind, para pré-processar a entrada durante a composição.

Na composição normal, a função, compose (>>)é usada para aplicar a função composta ao resultado de seu predecessor em sequência. É importante ressaltar que a função que está sendo composta é necessária para lidar com todos os cenários de sua entrada.

(x -> y) >> (y -> z)

Esse design pode ser aprimorado reestruturando a entrada para que os estados relevantes sejam mais facilmente interrogados. Portanto, em vez de simplesmente yo valor pode se tornar Mb, por exemplo, (is_OK, b)se yincluir uma noção de validade.

Por exemplo, quando a entrada é possivelmente apenas um número, em vez de retornar uma sequência que pode conter um número obedientemente ou não, você pode reestruturar o tipo em um boolindicando a presença de um número válido e um número na tupla, como bool * float,. As funções compostas não precisariam mais analisar uma sequência de entrada para determinar se existe um número, mas poderiam apenas inspecionar a boolparte de uma tupla.

(Ma -> Mb) >> (Mb -> Mc)

Aqui, novamente, a composição ocorre naturalmente composee, portanto, cada função deve lidar com todos os cenários de sua entrada individualmente, embora isso agora seja muito mais fácil.

No entanto, e se pudéssemos externalizar o esforço de interrogatório nos momentos em que lidar com um cenário é rotineiro? Por exemplo, e se o nosso programa não fizer nada quando a entrada não estiver boa como em quando is_OKestá false. Se isso fosse feito, as funções compostas não precisariam lidar com esse cenário, simplificando dramaticamente seu código e efetuando outro nível de reutilização.

Para alcançar essa externalização, poderíamos usar uma função bind (>>=),, para executar o compositioninvés de compose. Como tal, em vez de simplesmente transferir valores da saída de uma função para a entrada de outra Bind, inspecionaria a Mparte Mae decidiria se e como aplicar a função composta à a. Obviamente, a função bindseria definida especificamente para o nosso particular, de Mmodo a poder inspecionar sua estrutura e executar o tipo de aplicação que desejamos. No entanto, o apode ser qualquer coisa, pois bindapenas passa o não ainspecionado para a função composta quando determina a aplicação necessária. Além disso, as próprias funções compostas não precisam mais lidar com oMparte da estrutura de entrada, simplificando-os. Conseqüentemente...

(a -> Mb) >>= (b -> Mc) ou mais sucintamente Mb >>= (b -> Mc)

Em resumo, uma mônada externaliza e, portanto, fornece comportamento padrão em relação ao tratamento de certos cenários de entrada, uma vez que a entrada é projetada para expô-los suficientemente. Esse design é um shell and contentmodelo em que o shell contém dados relevantes para a aplicação da função composta e é interrogado e permanece disponível apenas para a bindfunção.

Portanto, uma mônada é três coisas:

  1. uma Mconcha para armazenar informações relevantes da mônada,
  2. uma bindfunção implementada para fazer uso dessas informações do shell em sua aplicação das funções compostas aos valores de conteúdo encontrados dentro do shell, e
  3. funções composíveis do formulário, a -> Mbproduzindo resultados que incluem dados de gerenciamento monádicos.

De um modo geral, a entrada para uma função é muito mais restritiva do que sua saída, o que pode incluir coisas como condições de erro; portanto, a Mbestrutura de resultados é geralmente muito útil. Por exemplo, o operador de divisão não retorna um número quando o divisor é 0.

Além disso, monads podem incluir funções de quebra automática que envolvem valores, ano tipo monádico Ma, e funções gerais a -> b, em funções monádicas a -> Mb, envolvendo os resultados após a aplicação. Obviamente bind, tais funções de wrap são específicas para M. Um exemplo:

let return a = [a]
let lift f a = return (f a)

O design da bindfunção pressupõe estruturas de dados imutáveis ​​e funções puras, outras coisas ficam complexas e garantias não podem ser feitas. Como tal, existem leis monádicas:

Dado...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Então...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitysignifica que bindpreserva a ordem da avaliação, independentemente de quando bindé aplicada. Ou seja, na definição de Associativityacima, a força de avaliação precoce do parêntesis bindingde fe gsó vai resultar em uma função que espera Ma, a fim de completar a bind. Portanto, a avaliação de Madeve ser determinada antes que seu valor possa ser aplicado fe esse resultado, por sua vez, aplicado g.


"... mas espero que outros achem útil" foi realmente útil para mim, apesar de todas as frases enfatizadas: D

Esta é a explicação mais concisa e clara das mônadas que eu já li / assisti / ouvi. Obrigado!
James

Há uma diferença importante entre Monad e Monoid. Mônada é uma regra para "compor" funções entre tipos diferentes , para que elas não formem uma operação binária conforme exigido pelo Monoids, veja aqui para obter mais detalhes: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

Sim. Você está certo. Seu artigo estava sobre minha cabeça :). No entanto, achei este tratamento muito útil (e o adicionei ao meu como orientação para outras pessoas). Graças as suas cabeças up: stackoverflow.com/a/7829607/1612190
George

2
Você pode ter confundido a teoria dos grupos algébricos com a teoria das categorias, de onde vem a Mônada. O primeiro é a teoria dos grupos algébricos, que não tem relação.
Dmitri Zaitsev

37

Uma mônada é, efetivamente, uma forma de "operador de tipo". Isso fará três coisas. Primeiro, ele "quebra" (ou converte) um valor de um tipo em outro (normalmente chamado de "tipo monádico"). Em segundo lugar, disponibilizará todas as operações (ou funções) no tipo subjacente no tipo monádico. Finalmente, ele fornecerá suporte para combinar a si próprio com outra mônada para produzir uma mônada composta.

A "talvez mônada" é essencialmente o equivalente a "tipos anuláveis" no Visual Basic / C #. Ele pega um tipo não anulável "T" e o converte em "Nullable <T>" e depois define o que todos os operadores binários significam em um Nullable <T>.

Os efeitos colaterais são representados de forma semelhante. É criada uma estrutura que contém descrições dos efeitos colaterais ao lado do valor de retorno de uma função. As operações "levantadas" copiam os efeitos colaterais à medida que os valores são passados ​​entre as funções.

Eles são chamados de "mônadas" em vez do nome mais fácil de entender de "operadores de tipo" por vários motivos:

  1. Mônadas têm restrições sobre o que podem fazer (consulte a definição para obter detalhes).
  2. Essas restrições, juntamente com o fato de haver três operações envolvidas, estão em conformidade com a estrutura de algo chamado mônada na Teoria das categorias, que é um ramo obscuro da matemática.
  3. Eles foram projetados por defensores de linguagens funcionais "puras"
  4. Defensores de linguagens funcionais puras, como ramos obscuros da matemática
  5. Como a matemática é obscura e as mônadas são associadas a estilos particulares de programação, as pessoas tendem a usar a palavra mônada como uma espécie de aperto de mão secreto. Por causa disso, ninguém se preocupou em investir em um nome melhor.

1
Mônadas não foram 'projetadas', elas foram aplicadas de um domínio (teoria das categorias) para outro (E / S em linguagens de programação puramente funcionais). Newton 'projetou' o cálculo?
Jared Updike

1
Os pontos 1 e 2 acima estão corretos e úteis. Os pontos 4 e 5 são uma espécie de ad hominem, mesmo que sejam mais ou menos verdadeiros. Eles realmente não ajudam a explicar mônadas.
Jared Updike

13
Re: 4, 5: O "Segredo do aperto de mão" é um arenque vermelho. A programação é cheia de jargões. Haskell simplesmente chama as coisas do que são, sem pretender redescobrir alguma coisa. Se já existe na matemática, por que criar um novo nome para ela? O nome não é realmente o motivo pelo qual as pessoas não recebem mônadas; eles são um conceito sutil. A pessoa comum provavelmente entende adição e multiplicação, por que não entende o conceito de um grupo abeliano? Porque é mais abstrato e geral e essa pessoa não fez o trabalho para entender o conceito. Uma mudança de nome não ajudaria.
Jared Updike

16
Suspiro ... Não estou atacando Haskell ... Estava fazendo uma piada. Então, eu realmente não entendo um pouco sobre ser "ad hominem". Sim, o cálculo foi "projetado". É por isso que, por exemplo, os alunos de cálculo aprendem a notação de Leibniz, e não as coisas nojentas que Netwton usou. Melhor design. Bons nomes ajudam a entender muito. Se eu chamei os Grupos Abelianos de "vagens de rugas distendidas", você pode ter problemas para me entender. Você pode estar dizendo "mas esse nome não faz sentido", ninguém jamais os chamaria assim. Para as pessoas que nunca ouviram falar da teoria das categorias, "mônada" parece um absurdo.
Scott Wisniewski

4
@ Scott: desculpe se meus extensos comentários fizeram parecer que eu estava ficando na defensiva sobre Haskell. Gosto do seu humor sobre o aperto de mão secreto e você notará que eu disse que é mais ou menos verdadeiro. :-) Se você chamasse os Grupos Abelianos de "vagens distendidas para rugas", cometeria o mesmo erro de tentar dar às mônadas um "nome melhor" (cf. F # "expressões computacionais"): o termo existe e as pessoas que se importam sabem o que as mônadas são, mas não o que são "coisas difusas quentes" (ou "expressões de computação"). Se eu entendi o uso do termo "operador de tipo" corretamente, existem muitos outros operadores de tipo além das mônadas.
Jared Updike

35

(Veja também as respostas em O que é uma mônada? )

Uma boa motivação para as Mônadas é o Você poderia ter inventado as Mônadas de sigfpe (Dan Piponi) ! (E talvez você já tenha) . Existem muitos outros tutoriais de mônada , muitos dos quais tentam explicar erroneamente mônadas em "termos simples" usando várias analogias: esta é a falácia do tutorial de mônada ; Evite-os.

Como o DR MacIver diz em Diga-nos por que seu idioma é péssimo :

Então, coisas que eu odeio em Haskell:

Vamos começar com o óbvio. Tutoriais da Mônada. Não, não mônadas. Especificamente os tutoriais. Eles são infinitos, exagerados e querido deus, eles são tediosos. Além disso, nunca vi nenhuma evidência convincente de que eles realmente ajudem. Leia a definição da classe, escreva algum código, supere o nome assustador.

Você diz que entende a mônada Talvez? Bom, você está a caminho. Comece a usar outras mônadas e, mais cedo ou mais tarde, você entenderá o que são mônadas em geral.

[Se você é orientado matematicamente, pode querer ignorar as dezenas de tutoriais e aprender a definição, ou seguir as aulas de teoria das categorias :) A parte principal da definição é que um Monad M envolve um "construtor de tipo" que define para cada tipo existente "T", um novo tipo "MT" e algumas maneiras de alternar entre os tipos "regulares" e "M".]

Além disso, surpreendentemente, uma das melhores introduções às mônadas é na verdade um dos primeiros trabalhos acadêmicos que apresentam mônadas, as mônadas de Philip Wadler para programação funcional . Na verdade, ele tem exemplos motivadores práticos e não triviais , ao contrário de muitos dos tutoriais artificiais existentes.


2
O único problema com o artigo de Wadler é que a notação é diferente, mas concordo que o artigo é bastante convincente e uma clara motivação concisa para aplicar mônadas.
Jared Updike

+1 para a "falácia do tutorial da mônada". Tutoriais em mônadas são semelhantes a vários tutoriais tentando explicar o conceito de números inteiros. Um tutorial diria: "1 é semelhante a uma maçã"; outro tutorial diz: "2 é como uma pera"; um terceiro diz: "3 é basicamente uma laranja". Mas você nunca obtém a imagem completa de um único tutorial. O que tirei disso é que as mônadas são um conceito abstrato que pode ser usado para muitos propósitos bem diferentes.
stakx - não contribuindo mais com

@stakx: Sim, é verdade. Mas não quis dizer que as mônadas são uma abstração que você não pode aprender ou não deveria aprender; só que é melhor aprendê-lo depois de ver exemplos concretos suficientes para perceber uma única abstração subjacente. Veja minha outra resposta aqui .
ShreevatsaR

5
Às vezes, sinto que existem muitos tutoriais que tentam convencer o leitor de que as mônadas são úteis, usando código que faz coisas complicadas ou úteis. Isso atrapalhou minha compreensão por meses. Eu não aprendo assim. Eu prefiro ver código extremamente simples, fazendo algo estúpido pelo qual posso passar mentalmente e não consegui encontrar esse tipo de exemplo. Não sei se o primeiro exemplo é uma mônada para analisar uma gramática complicada. Eu posso aprender se é uma mônada para somar números inteiros.
Rafael S. Calsaverini

Mencionando única construtor de tipo é incompleto: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

Mônadas devem controlar o fluxo que tipos de dados abstratos são para dados.

Em outras palavras, muitos desenvolvedores estão confortáveis ​​com a idéia de Conjuntos, Listas, Dicionários (ou Hashes ou Mapas) e Árvores. Dentro desses tipos de dados, há muitos casos especiais (por exemplo, InsertionOrderPreservingIdentityHashMap).

No entanto, quando confrontados com o "fluxo" do programa, muitos desenvolvedores não foram expostos a muito mais construções do que se, switch / case, faz, while, goto (grr) e (talvez) fechamentos.

Assim, uma mônada é simplesmente uma construção de fluxo de controle. Uma frase melhor para substituir a mônada seria 'tipo de controle'.

Como tal, uma mônada possui slots para lógica de controle, instruções ou funções - o equivalente em estruturas de dados seria dizer que algumas estruturas de dados permitem adicionar dados e removê-los.

Por exemplo, a mônada "if":

if( clause ) then block

na sua forma mais simples, tem dois slots - uma cláusula e um bloco. A ifmônada geralmente é criada para avaliar o resultado da cláusula e, se não for falsa, avaliar o bloco. Muitos desenvolvedores não são apresentados às mônadas quando aprendem 'se', e simplesmente não é necessário entender as mônadas para escrever uma lógica eficaz.

As mônadas podem se tornar mais complicadas, da mesma maneira que as estruturas de dados podem se tornar mais complicadas, mas existem muitas categorias amplas de mônadas que podem ter semântica semelhante, mas implementações e sintaxe diferentes.

Obviamente, da mesma maneira que as estruturas de dados podem ser iteradas sobre, ou atravessadas, mônadas podem ser avaliadas.

Os compiladores podem ou não ter suporte para mônadas definidas pelo usuário. Haskell certamente faz. Ioke possui alguns recursos semelhantes, embora o termo mônada não seja usado no idioma.


14

Meu tutorial favorito do Monad:

http://www.haskell.org/haskellwiki/All_About_Monads

(de 170.000 resultados em uma pesquisa no Google por "mônada tutorial"!)

@Stu: O objetivo das mônadas é permitir que você adicione (geralmente) a semântica seqüencial ao código puro; você pode até compor mônadas (usando Monad Transformers) e obter semânticas combinadas mais interessantes e complicadas, como analisar com tratamento de erros, estado compartilhado e registro, por exemplo. Tudo isso é possível em código puro, as mônadas apenas permitem abstraí-lo e reutilizá-lo em bibliotecas modulares (sempre boas em programação), além de fornecer sintaxe conveniente para torná-lo imperativo.

O Haskell já possui sobrecarga de operador [1]: ele usa classes de tipos da mesma forma que as interfaces em Java ou C #, mas o Haskell também permite também tokens não alfanuméricos como + && e> como identificadores de infix. É apenas uma sobrecarga de operador na sua maneira de ver se você quer dizer "sobrecarregar o ponto e vírgula" [2]. Parece magia negra e pedir problemas para "sobrecarregar o ponto e vírgula" (imagine os hackers Perl se divertindo com essa idéia), mas o ponto é que sem mônadas não há ponto e vírgula, pois o código puramente funcional não exige ou permite o seqüenciamento explícito.

Tudo isso parece muito mais complicado do que precisa. O artigo de sigfpe é bem legal, mas usa Haskell para explicá-lo, o que meio que falha em quebrar o problema do ovo e da galinha de entender Haskell para agredir mônadas e entender Mônadas para agredir Haskell.

[1] Este é um problema separado das mônadas, mas as mônadas usam o recurso de sobrecarga do operador da Haskell.

[2] Isso também é uma simplificação excessiva, pois o operador para encadear ações monádicas é >> = (pronunciado "bind"), mas há açúcar sintático ("do") que permite usar chaves e ponto e vírgula e / ou recuo e novas linhas.


9

Ultimamente, tenho pensado em mônadas de uma maneira diferente. Eu tenho pensado neles como abstraindo a ordem de execução de uma maneira matemática, o que possibilita novos tipos de polimorfismo.

Se você estiver usando uma linguagem imperativa e escrever algumas expressões em ordem, o código SEMPRE será executado exatamente nessa ordem.

E, no caso simples, quando você usa uma mônada, a mesma sensação é: você define uma lista de expressões que ocorrem em ordem. Exceto pelo fato de que, dependendo da mônada que você usa, seu código pode ser executado em ordem (como na mônada de IO), em paralelo sobre vários itens de uma vez (como na mônada de lista), ele pode parar no meio (como na mônada Maybe) , ele pode pausar parcialmente para ser retomado mais tarde (como em uma mônada de Retoma), pode retroceder e começar do começo (como em uma mônada de Transação) ou pode retroceder parcialmente para tentar outras opções (como em uma mônada da lógica) .

E como as mônadas são polimórficas, é possível executar o mesmo código em mônadas diferentes, dependendo de suas necessidades.

Além disso, em alguns casos, é possível combinar mônadas (com transformadores de mônada) para obter vários recursos ao mesmo tempo.


9

Ainda sou novo em mônadas, mas pensei em compartilhar um link que achei muito bom de ler (COM FOTOS !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ mônadas-para-leigo / (sem afiliação)

Basicamente, o conceito acolhedor e difuso que obtive do artigo foi o de que as mônadas são basicamente adaptadores que permitem que funções diferentes funcionem de maneira composível, ou seja, seja capaz de agrupar várias funções e misturá-las e combiná-las sem se preocupar com retorno inconsistente tipos e tal. Portanto, a função BIND é responsável por manter maçãs com maçãs e laranjas com laranjas quando estamos tentando fazer esses adaptadores. E a função LIFT é responsável por pegar as funções de "nível inferior" e "atualizá-las" para que funcionem com as funções BIND e também sejam compostas.

Espero ter acertado e, mais importante, espero que o artigo tenha uma visão válida sobre as mônadas. Se nada mais, este artigo ajudou a estimular meu apetite por aprender mais sobre mônadas.


Os exemplos de python tornaram fácil a compreensão! Obrigado por compartilhar.
Ryan Efendy

8

Além das excelentes respostas acima, deixe-me oferecer um link para o seguinte artigo (de Patrick Thomson), que explica as mônadas relacionando o conceito à biblioteca JavaScript jQuery (e sua maneira de usar o "encadeamento de métodos" para manipular o DOM) : jQuery é uma mônada

A documentação do jQuery em si não se refere ao termo "mônada", mas fala sobre o "padrão do construtor", que provavelmente é mais familiar. Isso não muda o fato de que você tem uma mônada adequada lá, talvez sem nem perceber.


Se você usa jQuery, esta explicação pode ser muito útil, especialmente se o seu Haskell não for forte
byteclub

10
JQuery enfaticamente não é uma mônada. O artigo vinculado está errado.
Tony Morris

1
Ser "enfático" não é muito convincente. Por alguma discussão útil sobre o tema, ver Is jQuery uma mônada - Stack Overflow
nealmcb

1
Veja também de Douglas Crackford Google Talk Mônadas e Gonads e seu código Javascript para fazer modads, expandindo sobre o comportamento semelhante de bibliotecas e promete AJAX: Douglas Crockford / mônada · GitHub
nealmcb


7

Uma mônada é uma maneira de combinar cálculos juntos que compartilham um contexto comum. É como construir uma rede de tubulações. Ao construir a rede, não há dados fluindo através dela. Mas, quando terminei de juntar todos os bits com 'bind' e 'return', invoco algo parecido runMyMonad monad datae os dados fluem através dos pipes.


1
Isso é mais parecido com Aplicável que com Mônada. Com o Mônadas, você precisa obter dados dos canais antes de escolher o próximo canal a ser conectado.
Peaker

sim, você descreve o candidato, não a mônada. A Mônada está construindo o próximo segmento de canal no local, dependendo dos dados que atingiram esse ponto, dentro do canal.
Will Ness

6

Na prática, o monad é uma implementação personalizada do operador de composição de funções que cuida de efeitos colaterais e valores incompatíveis de entrada e retorno (para encadeamento).



5

As duas coisas que me ajudaram a aprender melhor foram:

Capítulo 8, "Analisadores Funcionais", do livro de Graham Hutton, Programming in Haskell . Isso não menciona mônadas, na verdade, mas se você puder ler o capítulo e realmente entender tudo nele, principalmente como uma sequência de operações de ligação é avaliada, você entenderá o interior das mônadas. Espere que isso faça várias tentativas.

O tutorial Tudo sobre mônadas . Isso fornece vários bons exemplos de uso deles, e devo dizer que a analogia no Appendex funcionou para mim.


5

O Monoid parece ser algo que garante que todas as operações definidas em um Monoid e em um tipo suportado sempre retornem um tipo suportado dentro do Monoid. Por exemplo, Qualquer número + Qualquer número = Um número, sem erros.

Considerando que divisão aceita dois fracionários e retorna um fracionário, que definiu divisão por zero como Infinito em haskell de alguma forma (que por acaso é um fracionário de alguma forma) ...

De qualquer forma, parece que as mônadas são apenas uma maneira de garantir que sua cadeia de operações se comporte de maneira previsível, e uma função que afirma ser Num -> Num, composta por outra função de Num-> Num chamada com x, não digamos, atire nos mísseis.

Por outro lado, se temos uma função que dispara os mísseis, podemos compor com outras funções que também disparam os mísseis, porque nossa intenção é clara - queremos disparar os mísseis - mas não tentamos imprimindo "Hello World" por algum motivo estranho.

Em Haskell, main é do tipo IO (), ou IO [()], a distiction é estranha e não discutirei, mas eis o que acho que acontece:

Se eu tiver main, quero que ele execute uma cadeia de ações, a razão pela qual eu executo o programa é produzir um efeito - geralmente através de IO. Assim, eu posso encadear operações de IO em conjunto, principalmente para - fazer IO, nada mais.

Se eu tentar fazer algo que não "retorne IO", o programa reclamará que a cadeia não flui, ou basicamente "Como isso se relaciona com o que estamos tentando fazer - uma ação de IO", parece forçar o programador manter sua linha de pensamento, sem se desviar e pensar em disparar os mísseis, enquanto cria algoritmos para classificação - o que não flui.

Basicamente, as mônadas parecem ser uma dica para o compilador de que "ei, você conhece essa função que retorna um número aqui, ela nem sempre funciona, às vezes pode produzir um número e às vezes nada, apenas mantenha isso em mente". Sabendo disso, se você tentar afirmar uma ação monádica, a ação monádica poderá funcionar como uma exceção de tempo de compilação dizendo "ei, isso não é realmente um número, isso PODE ser um número, mas você não pode assumir isso, faça algo para garantir que o fluxo seja aceitável ". o que impede o comportamento imprevisível do programa - em uma extensão justa.

Parece que as mônadas não são sobre pureza, nem controle, mas sobre a manutenção de uma identidade de uma categoria na qual todo comportamento é previsível e definido, ou não é compilado. Você não pode fazer nada quando se espera que faça algo e não pode fazer algo se se espera que não faça nada (visível).

A maior razão que eu pude pensar para o Monads é - vá para o código Procedural / OOP, e você perceberá que não sabe onde o programa começa nem termina, tudo o que você vê é muito salto e muita matemática , mágica e mísseis. Você não será capaz de mantê-lo e, se puder, gastará muito tempo pensando em todo o programa antes de entender qualquer parte dele, porque a modularidade nesse contexto é baseada em "seções" interdependentes do código, em que o código é otimizado para ser o mais relacionado possível, com a promessa de eficiência / inter-relação. As mônadas são muito concretas e bem definidas por definição, e garantem que o fluxo do programa seja possível de analisar e isolar partes difíceis de analisar - como elas próprias são mônadas. Uma mônada parece ser uma " ou destruir o universo ou até distorcer o tempo - não temos idéia nem garantia de que É O QUE É. Uma mônada garante que é o que é. o que é muito poderoso. ou destruir o universo ou até distorcer o tempo - não temos idéia nem garantia de que É O QUE É. Uma mônada garante que é o que é. o que é muito poderoso.

Todas as coisas no "mundo real" parecem ser mônadas, no sentido de que estão vinculadas por leis definidas e observáveis ​​que impedem a confusão. Isso não significa que precisamos imitar todas as operações desse objeto para criar classes; em vez disso, podemos simplesmente dizer "um quadrado é um quadrado", nada além de um quadrado, nem mesmo um retângulo nem um círculo, e "um quadrado tem área do comprimento de uma de suas dimensões existentes multiplicado por ele mesmo.Não importa qual quadrado você tenha, se for um quadrado no espaço 2D, sua área não pode ser outra coisa senão seu comprimento ao quadrado, é quase trivial provar. não precisamos fazer afirmações para garantir que nosso mundo seja o que é, apenas usamos implicações da realidade para impedir que nossos programas caiam no rumo.

Estou praticamente garantido que estou errado, mas acho que isso poderia ajudar alguém lá fora, por isso espero que ajude alguém.


5

No contexto do Scala, você encontrará a definição a seguir mais simples. Basicamente, o flatMap (ou bind) é 'associativo' e existe uma identidade.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

Por exemplo

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

NOTA Estritamente falando, a definição de uma Mônada na programação funcional não é a mesma que a definição de uma Mônada na Teoria das Categorias , que é definida nos turnos de mape flatten. Embora eles sejam equivalentes sob certos mapeamentos. Esta apresentação é muito boa: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


5

Essa resposta começa com um exemplo motivador, funciona através do exemplo, deriva um exemplo de mônada e define formalmente "mônada".

Considere estas três funções no pseudocódigo:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

fpega um par ordenado do formulário <x, messages>e retorna um par ordenado. Ele deixa o primeiro item intocado e anexa "called f. "ao segundo item. O mesmo com g.

Você pode compor essas funções e obter seu valor original, juntamente com uma sequência que mostra em qual ordem as funções foram chamadas:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Você não gosta do fato de que fe gsão responsáveis para acrescentar suas próprias mensagens de log para as informações de registro anterior. (Imaginem por causa do argumento de que, em vez de acrescentar cordas, fe gdeve executar lógica complicado no segundo item do par Seria uma dor de repetição que a lógica complicada em dois -. Ou mais -. Funções diferentes)

Você prefere escrever funções mais simples:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Mas veja o que acontece quando você os compõe:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

O problema é que passar um par para uma função não fornece o que você deseja. Mas e se você pudesse alimentar um par em uma função:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Leia feed(f, m)como "feed minto f". Para alimentar um par <x, messages>em uma função fé passar x em f, ficar <y, message>fora de f, e retornar <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Observe o que acontece quando você faz três coisas com suas funções:

Primeiro: se você quebrar um valor e depois alimentar o par resultante em uma função:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

É o mesmo que passar o valor para a função.

Segundo: se você alimentar um par em wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Isso não muda o par.

Terceiro: se você definir uma função que recebe xe alimenta g(x)em f:

h(x) := feed(f, g(x))

e alimente um par:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

É o mesmo que alimentar o par ge alimentar o par resultante f.

Você tem mais de uma mônada. Agora você só precisa saber sobre os tipos de dados no seu programa.

Que tipo de valor é <x, "called f. ">? Bem, isso depende de que tipo de valor xé. Se xfor do tipo t, então seu par é um valor do tipo "par de te sequência". Ligue para esse tipo M t.

Mé um construtor de tipos: Msozinho não se refere a um tipo, mas M _se refere a um tipo depois que você preencher o espaço em branco com um tipo. An M inté um par de um int e uma string. An M stringé um par de uma string e uma string. Etc.

Parabéns, você criou uma mônada!

Formalmente, sua mônada é a tupla <M, feed, wrap>.

Uma mônada é uma tupla em <M, feed, wrap>que:

  • M é um construtor de tipos.
  • feedpega uma (função que pega tae retorna an M u) e an M te retorna an M u.
  • wrappega um ve retorna um M v.

t,, ue vexistem três tipos que podem ou não ser iguais. Uma mônada satisfaz as três propriedades que você provou para sua mônada específica:

  • Alimentar um embrulhado tem uma função é o mesmo que passar o desembrulhado tpara a função.

    Formalmente: feed(f, wrap(x)) = f(x)

  • Alimentando um M tem wrapnada contribui para a M t.

    Formalmente: feed(wrap, m) = m

  • Alimentando um M t(chame m) em uma função que

    • passa o tparag
    • recebe um M u(chame n) deg
    • alimenta nemf

    é o mesmo que

    • alimentando memg
    • obtenção ndeg
    • alimentando nemf

    Formalmente: feed(h, m) = feed(f, feed(g, m))ondeh(x) := feed(f, g(x))

Normalmente, feedé chamado bind(AKA >>=em Haskell) e wrapé chamado return.


5

Vou tentar explicar Monadno contexto de Haskell.

Na programação funcional, a composição da função é importante. Ele permite que nosso programa seja composto por funções pequenas e fáceis de ler.

Digamos que temos duas funções: g :: Int -> Stringe f :: String -> Bool.

Nós podemos fazer (f . g) x, que é exatamente o mesmo que f (g x), onde xé um Intvalor.

Ao fazer a composição / aplicar o resultado de uma função a outra, é importante ter os tipos correspondentes. No caso acima, o tipo do resultado retornado por gdeve ser o mesmo que o tipo aceito por f.

Mas, às vezes, os valores estão em contextos, e isso torna um pouco menos fácil alinhar os tipos. (Ter valores em contextos é muito útil. Por exemplo, o Maybe Inttipo representa um Intvalor que pode não estar lá, o IO Stringtipo representa um Stringvalor que existe como resultado da execução de alguns efeitos colaterais.)

Digamos que agora temos g1 :: Int -> Maybe Stringe f1 :: String -> Maybe Bool. g1e f1são muito semelhantes ge frespectivamente.

Não podemos fazer (f1 . g1) xou f1 (g1 x), onde xestá um Intvalor. O tipo do resultado retornado por g1não é o que f1espera.

Poderíamos compor fe gcom o .operador, mas agora não podemos compor f1e g1com .. O problema é que não podemos passar diretamente um valor em um contexto para uma função que espera um valor que não esteja em um contexto.

Não seria bom se apresentássemos um operador para compor g1e f1, para que possamos escrever (f1 OPERATOR g1) x? g1retorna um valor em um contexto. O valor será retirado do contexto e aplicado f1. E sim, temos um operador assim. É <=<.

Também temos o >>=operador que faz exatamente a mesma coisa para nós, embora em uma sintaxe ligeiramente diferente.

Nós escrevemos: g1 x >>= f1. g1 xé um Maybe Intvalor. O >>=operador ajuda a tirar esse Intvalor do contexto "talvez não esteja lá" e aplicá-lo f1. O resultado de f1, que é a Maybe Bool, será o resultado de toda a >>=operação.

E, finalmente, por que é Monadútil? Uma vez que Monadé a classe de tipo que define o >>=operador, muito da mesma forma como a Eqclasse de tipo que define a ==e /=operadores.

Para concluir, a Monadclasse type define o >>=operador que nos permite passar valores em um contexto (chamamos esses valores monádicos) a funções que não esperam valores em um contexto. O contexto será tratado.

Se há uma coisa a lembrar aqui, é que Monadpermite a composição de funções que envolve valores em contextos .



IOW, o Monad é um protocolo de chamada de função generalizada.
Will Ness

Você responde é o mais útil na minha opinião. Embora eu deva dizer que acho que a ênfase precisa estar no fato de que as funções às quais você se refere não envolvem apenas valores em contextos, elas ativamente colocam valores em contextos. Assim, por exemplo, uma função, f :: ma -> mb comporia muito facilmente com outra função, g :: mb -> m c. Mas monads (ligam-se especificamente) nos permite perpetuamente funções compor o que colocou a sua entrada no mesmo contexto, sem nós que precisam para tomar o valor fora desse contexto primeiro (o que efetivamente remover informações a partir do valor)
James

@ James Eu acho que deve ser a ênfase para os functores?
Jonas

@ Jonas, acho que não expliquei direito. Quando digo que as funções colocam valores em contextos, quero dizer que elas têm o tipo (a -> mb). Isso é muito útil, pois colocar um valor em um contexto adiciona novas informações, mas normalmente seria difícil encadear um (a -> mb) e um (b -> mc) juntos, pois não podemos simplesmente tirar o valor do contexto. Portanto, teríamos que usar algum processo complicado para encadear essas funções de maneira sensata, dependendo do contexto específico, e as mônadas nos permitem fazer isso de maneira consistente, independentemente do contexto.
James

5

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Prólogo

O operador $de aplicação de funções

forall a b. a -> b

é definido canonicamente

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

em termos de aplicação da função primitiva de Haskell f x( infixl 10).

Composição .é definido em termos de $como

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

e satisfaz as equivalências forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.é associativo e idé sua identidade direita e esquerda.

O triplo Kleisli

Na programação, uma mônada é um construtor do tipo functor com uma instância da classe do tipo mônada. Existem várias variantes equivalentes de definição e implementação, cada uma carregando intuições ligeiramente diferentes sobre a abstração da mônada.

Um functor é um tipo de construtor fde tipo * -> *com uma instância da classe de tipo de functor.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

Além de seguir o protocolo de tipo imposto estaticamente, as instâncias da classe de tipo functor devem obedecer às leis algébricas do functor forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Os cálculos de functor têm o tipo

forall f t. Functor f => f t

Uma computação c rconsiste em resultados r dentro do contexto c .

Funções monádicas unárias ou setas Kleisli têm o tipo

forall m a b. Functor m => a -> m b

As setas de Kleisi são funções que recebem um argumento ae retornam uma computação monádica m b.

Mônadas são canonicamente definidas em termos do triplo de Kleisli forall m. Functor m =>

(m, return, (=<<))

implementado como a classe de tipo

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

A identidade Kleisli return é uma flecha Kleisli que promove um valor tno contexto monádico m. O aplicativo Extension ou Kleisli =<< aplica uma seta Kleisli a -> m baos resultados de uma computação m a.

A composição de Kleisli <=< é definida em termos de extensão como

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< compõe duas setas Kleisli, aplicando a seta esquerda aos resultados da aplicação da seta direita.

Instâncias da classe do tipo mônada devem obedecer às leis da mônada , mais elegantemente declaradas em termos da composição de Kleisli:forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<é associativo e returné sua identidade direita e esquerda.

Identidade

O tipo de identidade

type Id t = t

é a função de identidade nos tipos

Id :: * -> *

Interpretado como um functor,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

Em Haskell canônico, a mônada de identidade é definida

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Opção

Um tipo de opção

data Maybe t = Nothing | Just t

codifica computação Maybe tque não necessariamente produz um resultado t, computação que pode "falhar". A opção monad é definida

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe bé aplicado a um resultado apenas se Maybe aproduz um resultado.

newtype Nat = Nat Int

Os números naturais podem ser codificados como números inteiros maiores ou iguais a zero.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Os números naturais não são fechados sob subtração.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

A opção monad cobre uma forma básica de tratamento de exceções.

(-? 20) <=< toNat :: Int -> Maybe Nat

Lista

A mônada da lista, sobre o tipo de lista

data [] t = [] | t : [t]

infixr 5 :

e sua operação monóide aditiva "anexa"

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

codifica a computação não linear[t] produzindo uma quantidade natural 0, 1, ...de resultados t.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

A extensão =<<concatena ++todas as listas [b]resultantes de aplicativos f xde uma seta de Kleisli a -> [b]em elementos de [a]uma única lista de resultados [b].

Deixe os divisores apropriados de um inteiro positivo nser

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

então

forall n.  let { f = f <=< divisors } in f n   =   []

Ao definir a classe do tipo mônada, em vez da extensão =<<, o padrão Haskell usa seu flip, o operador de ligação>>= .

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

Por uma questão de simplicidade, esta explicação usa a hierarquia de classes de tipo

class              Functor f
class Functor m => Monad m

Em Haskell, a hierarquia padrão atual é

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

porque não apenas toda mônada é um functor, mas todo aplicativo é um functor e toda mônada também é um aplicativo.

Usando a mônada da lista, o pseudocódigo imperativo

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

traduz aproximadamente para o bloco do ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

a compreensão mônada equivalente ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

e a expressão

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

Compreensões de notação e mônada são açúcar sintático para expressões de ligação aninhadas. O operador de ligação é usado para ligação de nome local dos resultados monádicos.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

Onde

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

A função de guarda está definida

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

onde o tipo de unidade ou "tupla vazia"

data () = ()

Mônadas aditivas que suportam escolha e falha podem ser abstraídas usando uma classe de tipo

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

onde faile <|>formar um monóideforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

e failé o elemento zero absorvente / aniquilador das mônadas aditivas

_ =<< fail  =  fail

Se em

guard (even p) >> return p

even pé verdade, então o guarda produz [()]e, pela definição de >>, a função constante local

\ _ -> return p

é aplicado ao resultado (). Se falso, o guarda produz a mônada da lista fail( []), que não produz resultado para a aplicação de uma flecha de Kleisli >>; portanto, isso pé pulado.

Estado

Infelizmente, as mônadas são usadas para codificar a computação com estado.

Um processador de estado é uma função

forall st t. st -> (t, st)

que transita um estado ste produz um resultado t. O estado st pode ser qualquer coisa. Nada, bandeira, contagem, matriz, manuseio, máquina, mundo.

O tipo de processadores de estado é geralmente chamado

type State st t = st -> (t, st)

A mônada do processador de estado é o * -> *functor classificado State st. As setas Kleisli da mônada do processador de estado são funções

forall st a b. a -> (State st) b

Em Haskell canônico, a versão lenta da mônada do processador de estado é definida

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Um processador de estado é executado fornecendo um estado inicial:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

O acesso do estado é fornecido por métodos primitivos gete putde abstração sobre mônadas com estado :

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stdeclara uma dependência funcional do tipo de estado stna mônada m; que a State t, por exemplo, determinará o tipo de estado como túnico.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

com o tipo de unidade usado analogamente voidem C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets é frequentemente usado com acessadores de campo de registro.

O equivalente da mônada do estado da variável threading

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

onde s0 :: Inté igualmente referencialmente transparente, mas infinitamente mais elegante e prático

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)é um cálculo do tipo State Int (), exceto pelo efeito equivalente a return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

A lei mônada da associatividade pode ser escrita em termos de >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

ou

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

Como na programação orientada a expressões (por exemplo, Rust), a última declaração de um bloco representa seu rendimento. O operador de ligação às vezes é chamado de "ponto e vírgula programável".

As primitivas da estrutura de controle de iteração da programação imperativa estruturada são emuladas monadicamente

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Entrada / Saída

data World

A mônada do processador de estado mundial de E / S é uma reconciliação do puro Haskell e do mundo real, da semântica operacional denotativa e imperativa funcional. Um análogo próximo da implementação estrita real:

type IO t = World -> (t, World)

A interação é facilitada por primitivas impuras

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

A impureza do código que usa IOprimitivas é permanentemente protocolada pelo sistema de tipos. Porque a pureza é incrível, o que acontece IO, permanece IO.

unsafePerformIO :: IO t -> t

Ou, pelo menos, deveria.

A assinatura de tipo de um programa Haskell

main :: IO ()
main = putStrLn "Hello, World!"

expande para

World -> ((), World)

Uma função que transforma um mundo.

Epílogo

A categoria cujos objetos são do tipo Haskell e quais dos morfismos são funções entre os tipos de Haskell é, “rápido e solto”, a categoria Hask.

Um functor Té um mapeamento de uma categoria Cpara uma categoria D; para cada objeto em Cum objeto emD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

e para cada morfismo em Cum morfismo emD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

onde X, Ysão objetos C. HomC(X, Y)é a classe de homomorfismo de todos os morfismos X -> Yem C. O functor deve preservar a identidade e composição do morfismo, a "estrutura" de C, em D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

A categoria Kleisli de uma categoria Cé dada por um triplo Kleisli

<T, eta, _*>

de um endofuncor

T : C -> C

( f), um morfismo de identidade eta( return) e um operador de extensão *( =<<).

Cada morfismo Kleisli em Hask

      f :  X -> T(Y)
      f :: a -> m b

pelo operador de extensão

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

recebe um morfismo na Haskcategoria Kleisli de

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

A composição na categoria Kleisli .Té dada em termos de extensão

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

e satisfaz os axiomas da categoria

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

aplicando as transformações de equivalência

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

em termos de extensão são canonicamente dadas

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Mônadas também podem ser definidas em termos não da extensão kleisliana, mas de uma transformação natural mu, na programação chamada join. Uma mônada é definida em termos de mutriplo sobre uma categoria C, de um endofuncor

     T :  C -> C
     f :: * -> *

e duas tranformações naturais

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

satisfazendo as equivalências

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

A classe do tipo mônada é então definida

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

A muimplementação canônica da opção mônada:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

A concatfunção

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

é o joinda mônada da lista.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

As implementações de joinpodem ser traduzidas do formulário de extensão usando a equivalência

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

A conversão reversa do muformulário para extensão é dada por

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Mas por que uma teoria tão abstrata deve ter alguma utilidade para a programação?

A resposta é simples: como cientistas da computação, valorizamos a abstração ! Quando projetamos a interface para um componente de software, queremos que ele revele o mínimo possível sobre a implementação. Queremos poder substituir a implementação por muitas alternativas, muitas outras 'instâncias' do mesmo 'conceito'. Quando projetamos uma interface genérica para muitas bibliotecas de programas, é ainda mais importante que a interface que escolhemos tenha uma variedade de implementações. É a generalidade do conceito de mônada que valorizamos tanto, porque a teoria das categorias é tão abstrata que seus conceitos são tão úteis para a programação.

Não é de surpreender, portanto, que a generalização das mônadas que apresentamos abaixo também tenha uma estreita conexão com a teoria das categorias. Mas enfatizamos que nosso objetivo é muito prático: não é "implementar a teoria das categorias", é encontrar uma maneira mais geral de estruturar as bibliotecas combinadoras. É simplesmente nossa boa sorte que os matemáticos já tenham feito grande parte do trabalho por nós!

de Generalizar mônadas a flechas por John Hughes


4

O que o mundo precisa é de outro post de mônada, mas acho que isso é útil na identificação de mônadas existentes na natureza.

Triângulo de Sierpinski

O acima é um fractal chamado triângulo de Sierpinski, o único fractal que me lembro de desenhar. Os fractais são estruturas auto-semelhantes, como o triângulo acima, em que as partes são semelhantes ao todo (neste caso, exatamente metade da escala do triângulo pai).

Mônadas são fractais. Dada uma estrutura de dados monádica, seus valores podem ser compostos para formar outro valor da estrutura de dados. É por isso que é útil para a programação, e é por isso que ocorre em muitas situações.


3
Você quer dizer "o que o mundo não precisa ..."? Analogia agradável embora!
groverboy

@ icc97 você está certo - o significado é claro o suficiente. Sarcasmo não intencional, desculpas ao autor.
groverboy

O que o mundo precisa é de outro tópico de confirmação que confirme um sarcasmo, mas se for lido com cuidado, escrevi, mas isso deve deixar claro.
Eugene Yokota


4

Deixe o " {| a |m}" abaixo representar alguns dados monádicos. Um tipo de dados que anuncia um a:

        (I got an a!)
          /        
    {| a |m}

A função, fsabe como criar uma mônada, se ela tivesse a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Aqui vemos a função f,, tenta avaliar uma mônada, mas é repreendida.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtion,, fencontra uma maneira de extrair o ausando >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Pouco fsabe, a mônada e >>=estão em conluio.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

Mas sobre o que eles realmente falam? Bem, isso depende da mônada. Falar apenas no resumo tem uso limitado; você precisa ter alguma experiência com mônadas específicas para aprofundar o entendimento.

Por exemplo, o tipo de dados Talvez

 data Maybe a = Nothing | Just a

tem uma instância de mônada que age da seguinte forma ...

Onde, se for o caso Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Mas para o caso de Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Portanto, a mônada Maybe permite que uma computação continue se ela realmente contiver a apublicidade, mas interrompe a computação se não contiver . O resultado, no entanto, ainda é um pedaço de dados monádicos, embora não seja a saída de f. Por esse motivo, a mônada Maybe é usada para representar o contexto de falha.

Mônadas diferentes se comportam de maneira diferente. Listas são outros tipos de dados com instâncias monádicas. Eles se comportam da seguinte maneira:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

Nesse caso, a função sabia como fazer uma lista a partir de sua entrada, mas não sabia o que fazer com entradas e listas extras. O vínculo >>=, ajudado fpela combinação de várias saídas. Incluo este exemplo para mostrar que, embora >>=seja responsável pela extração a, ele também tem acesso à eventual saída vinculada de f. De fato, ele nunca extrairá nenhum, a amenos que saiba que a saída final tem o mesmo tipo de contexto.

Existem outras mônadas usadas para representar diferentes contextos. Aqui estão algumas caracterizações de mais alguns. A IOmônada na verdade não tem a, mas conhece um cara e vai conseguir isso apara você. A State stmônada tem um estoque secreto do stqual passará para fdebaixo da mesa, mesmo que fapenas tenha pedido uma a. A Reader rmônada é semelhante a State st, embora só permita fver r.

O ponto em tudo isso é que qualquer tipo de dado declarado como Mônada está declarando algum tipo de contexto para extrair um valor da Mônada. O grande ganho de tudo isso? Bem, é fácil o suficiente para calcular um cálculo com algum tipo de contexto. No entanto, pode ficar confuso ao reunir vários cálculos carregados de contexto. As operações da mônada cuidam de resolver as interações do contexto para que o programador não precise.

Observe que esse uso >>=facilita a bagunça, afastando parte da autonomia f. Ou seja, no caso acima, Nothingpor exemplo, fnão consegue mais decidir o que fazer no caso de Nothing; está codificado >>=. Essa é a troca. Se fosse necessário fdecidir o que fazer no caso de Nothing, fdeveria ter sido uma função de Maybe apara Maybe b. Nesse caso, Maybeser mônada é irrelevante.

Observe, no entanto, que às vezes um tipo de dados não exporta seus construtores (olhando para o IO) e, se queremos trabalhar com o valor anunciado, temos poucas opções, a não ser trabalhar com sua interface monádica.


3

Uma mônada é uma coisa usada para encapsular objetos que mudam de estado. É frequentemente encontrado em idiomas que, de outra forma, não permitem que você tenha um estado modificável (por exemplo, Haskell).

Um exemplo seria para E / S de arquivo.

Você seria capaz de usar uma mônada para E / S de arquivo para isolar a natureza do estado de alteração apenas no código que usava a mônada. O código dentro da Mônada pode efetivamente ignorar a mudança do estado do mundo fora da Mônada - isso facilita muito o raciocínio sobre o efeito geral do seu programa.


3
Pelo que entendi, as mônadas são mais do que isso. Encapsular o estado mutável em linguagens funcionais "puras" é apenas uma aplicação de mônadas.
thSoft
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.