Quem primeiro disse o seguinte?
Uma mônada é apenas um monóide na categoria de endofunitores, qual é o problema?
E, em uma nota menos importante, isso é verdade e, em caso afirmativo, você poderia dar uma explicação (espero que possa ser entendida por alguém que não tenha muita experiência com Haskell)?
haskell
monads
category-theory
monoids
Roman A. Taycher
fonte
fonte
Respostas:
Esse fraseado em particular é de James Iry, de sua divertida História Breve, Incompleta e Muito Errada de Linguagens de Programação , na qual ele atribui ficcionalmente a Philip Wadler.
A citação original é de Saunders Mac Lane, em Categories for the Working Mathematician , um dos textos fundamentais da Teoria das Categorias. Aqui está o contexto , que é provavelmente o melhor lugar para aprender exatamente o que isso significa.
Mas vou dar uma facada. A frase original é esta:
X aqui é uma categoria. Endofuncionadores são functores de uma categoria para si (que geralmente é tudo
Functor
s no que diz respeito a programadores funcionais, já que eles lidam principalmente com apenas uma categoria; a categoria de tipos - mas eu discordo). Mas você pode imaginar outra categoria que é a categoria de "endofuncionadores em X ". Essa é uma categoria em que os objetos são endofuncionadores e os morfismos são transformações naturais.E desses endofunidores, alguns deles podem ser mônadas. Quais são as mônadas? Exatamente os que são monoidais em um sentido particular. Em vez de especificar o mapeamento exato de mônadas a monoides (já que Mac Lane faz isso muito melhor do que eu esperava), colocarei suas respectivas definições lado a lado e deixarei você comparar:
Um monóide é ...
... satisfazendo estas leis:
Uma mônada é ...
* -> *
com umaFunctor
instância)join
em Haskell)return
em Haskell)... satisfazendo estas leis:
Com um pouco de estrabismo, você poderá ver que essas duas definições são instâncias do mesmo conceito abstrato .
fonte
S
for um tipo, tudo o que você pode fazer ao escrever uma funçãof :: () -> S
é escolher algum termo específico do tipoS
(um "elemento", se desejar) e retornar isso ... você não recebeu informações reais sobre o argumento, portanto não há como alterar o comportamento da função. Portanto,f
deve ser uma função constante que apenas retorna a mesma coisa todas as vezes.()
("Unidade") é o objeto terminal da categoria Hask , e não é coincidência que exista exatamente 1 valor (não divergente) que o habita.Intuitivamente, acho que o que o vocabulário matemático sofisticado está dizendo é o seguinte:
Monoid
Um monóide é um conjunto de objetos e um método para combiná-los. Monoides conhecidos são:
Existem exemplos mais complexos também.
Além disso, todo monóide tem uma identidade , que é o elemento "no-op" que não tem efeito quando você o combina com outra coisa:
Finalmente, um monóide deve ser associativo . (você pode reduzir uma longa sequência de combinações da maneira que quiser, desde que não altere a ordem dos objetos da esquerda para a direita). A adição está OK ((5 + 3) +1 == 5+ (3+ 1)), mas a subtração não é ((5-3) -1! = 5- (3-1)).
Mônada
Agora, vamos considerar um tipo especial de conjunto e uma maneira especial de combinar objetos.
Objetos
Suponha que seu conjunto contenha objetos de um tipo especial: funções . E essas funções têm uma assinatura interessante: elas não carregam números para números ou cadeias para cadeias. Em vez disso, cada função carrega um número para uma lista de números em um processo de duas etapas.
Exemplos:
Combinando Objetos
Além disso, nossa maneira de combinar funções é especial. Uma maneira simples de combinar função é composição : vamos pegar nossos exemplos acima e compor cada função consigo mesma:
Sem aprofundar a teoria dos tipos, o ponto é que você pode combinar dois números inteiros para obter um número inteiro, mas nem sempre é possível compor duas funções e obter uma função do mesmo tipo. (Funções com o tipo a -> a serão compostas, mas a-> [a] não será.)
Então, vamos definir uma maneira diferente de combinar funções. Quando combinamos duas dessas funções, não queremos "dobrar" os resultados.
Aqui está o que fazemos. Quando queremos combinar duas funções F e G, seguimos esse processo (chamado de ligação ):
De volta aos nossos exemplos, vamos combinar (vincular) uma função consigo mesma usando esta nova maneira de "vincular" funções:
Essa maneira mais sofisticada de combinar funções é associativa (seguindo como a composição das funções é associativa quando você não está fazendo o material de embalagem sofisticado).
Amarrando tudo junto,
Notas
Existem várias maneiras de "agrupar" os resultados. Você pode fazer uma lista, um conjunto ou descartar tudo, exceto o primeiro resultado, observando se não há resultados, anexar um sidecar de estado, imprimir uma mensagem de log, etc.
Eu brinquei um pouco com as definições na esperança de transmitir a idéia essencial intuitivamente.
Simplifiquei um pouco as coisas insistindo que nossa mônada opera em funções do tipo a -> [a] . De fato, as mônadas trabalham em funções do tipo a -> mb , mas a generalização é uma espécie de detalhe técnico que não é o principal insight.
fonte
a -> [b]
ec -> [d]
(você só pode fazer isso seb
=c
), este não chega a descrever um monoid. Na verdade, é a operação de nivelamento que você descreveu, em vez da composição da função, que é o "operador monóide".a -> [a]
, isso seria um monóide (porque você reduziria a categoria Kleisli a um único objeto e qualquer categoria de apenas um objeto é por definição um monóide!), mas não capturaria toda a generalidade da mônada.Primeiro, as extensões e bibliotecas que vamos usar:
Destes,
RankNTypes
é o único que é absolutamente essencial para o abaixo. Certa vez, escrevi uma explicação deRankNTypes
que algumas pessoas parecem ter achado úteis , então vou me referir a isso.Citando a excelente resposta de Tom Crockett , temos:
Como traduzimos isso para o código Haskell? Bem, vamos começar com a noção de uma transformação natural :
Um tipo da forma
f :-> g
é análogo a um tipo de função, mas, em vez de pensar nela como uma função entre dois tipos (do tipo*
), pense nele como um morfismo entre dois functores (cada um do tipo* -> *
). Exemplos:Basicamente, em Haskell, transformações naturais são funções de algum tipo
f x
para outro tipo, deg x
modo que ax
variável type é "inacessível" para o chamador. Por exemplo,sort :: Ord a => [a] -> [a]
não pode ser transformado em uma transformação natural, porque é "exigente" sobre quais tipos podemos instanciara
. Uma maneira intuitiva que costumo usar para pensar sobre isso é o seguinte:Agora, com isso fora do caminho, vamos abordar as cláusulas da definição.
A primeira cláusula é "um endofuncor, T: X -> X ". Bem, todo mundo
Functor
em Haskell é um endofuncor no que as pessoas chamam de "categoria Hask", cujos objetos são do tipo Haskell (do tipo*
) e cujos morfismos são funções de Haskell. Parece uma afirmação complicada, mas na verdade é muito trivial. Tudo o que isso significa é que aFunctor f :: * -> *
fornece os meios de construir um tipof a :: *
para anya :: *
e uma função afmap f :: f a -> f b
partir de anyf :: a -> b
, e que eles obedecem às leis do functor.Segunda cláusula: o
Identity
functor em Haskell (que acompanha a plataforma, para que você possa importá-lo) é definido desta maneira:Portanto, a transformação natural η: I -> T da definição de Tom Crockett pode ser escrita dessa maneira para qualquer
Monad
instânciat
:Terceira cláusula: A composição de dois functores em Haskell pode ser definida desta maneira (que também vem com a Plataforma):
Portanto, a transformação natural μ: T × T -> T da definição de Tom Crockett pode ser escrita assim:
A afirmação de que este é um monóide na categoria de endofunitores significa que
Compose
(parcialmente aplicado apenas aos dois primeiros parâmetros) é associativo, e esseIdentity
é o seu elemento de identidade. Ou seja, que os seguintes isomorfismos são válidos:Compose f (Compose g h) ~= Compose (Compose f g) h
Compose f Identity ~= f
Compose Identity g ~= g
Isso é muito fácil de provar porque,
Compose
eIdentity
ambos são definidos comonewtype
, e os Relatórios Haskell definem a semânticanewtype
como um isomorfismo entre o tipo que está sendo definido e o tipo de argumento para onewtype
construtor de dados do. Então, por exemplo, vamos provarCompose f Identity ~= f
:fonte
Natural
novo tipo, não consigo descobrir o que a(Functor f, Functor g)
restrição está fazendo. Você poderia explicar?Functor
restrições, pois elas não parecem necessárias. Se você não concordar, sinta-se à vontade para adicioná-los novamente.join
é definido. E essejoin
é o morfismo da projeção. Mas eu não tenho certeza.Nota: Não, isso não é verdade. Em algum momento, houve um comentário sobre essa resposta do próprio Dan Piponi dizendo que a causa e o efeito aqui eram exatamente o oposto: ele escreveu seu artigo em resposta à piada de James Iry. Mas parece ter sido removido, talvez por alguma ordem compulsiva.
Abaixo está a minha resposta original.
É bem possível que Iry tenha lido From Monoids to Monads , um post no qual Dan Piponi (sigfpe) deriva mônadas de monoides em Haskell, com muita discussão sobre a teoria das categorias e menção explícita à "categoria dos endofuncionais em Hask ". De qualquer forma, qualquer pessoa que se pergunte o que significa uma mônada ser um monóide na categoria de endofuncionadores pode se beneficiar da leitura dessa derivação.
fonte
:-)
.Cheguei a este post com o intuito de entender melhor a inferência da citação infame da Teoria da Categoria para o Matemático de Trabalho de Mac Lane .
Ao descrever o que é algo, geralmente é igualmente útil descrever o que não é.
O fato de Mac Lane usar a descrição para descrever uma Mônada pode implicar que ela descreve algo único para as Mônadas. Tenha paciencia comigo. Para desenvolver uma compreensão mais ampla da afirmação, acredito que precisa ficar claro que ele não está descrevendo algo que é exclusivo das mônadas; a declaração descreve igualmente Aplicante e Setas, entre outros. Pela mesma razão, podemos ter dois monoides em Int (soma e produto), podemos ter vários monoides em X na categoria de endofunitores. Mas há ainda mais semelhanças.
Tanto a Mônada quanto a Requerente atendem aos critérios:
(por exemplo, no dia a dia
Tree a -> List b
, mas na categoriaTree -> List
)Tree -> List
apenasList -> List
.A instrução usa "Category of ..." Isso define o escopo da instrução. Como um exemplo, a Categoria Functor descreve o âmbito de
f * -> g *
, ou seja,Any functor -> Any functor
, por exemplo,Tree * -> List *
ouTree * -> Tree *
.O que uma declaração categórica não especifica descreve onde tudo é permitido .
Nesse caso, dentro dos functores,
* -> *
akaa -> b
não é especificado, o que significaAnything -> Anything including Anything else
. Como minha imaginação pula para Int -> String, ela também incluiInteger -> Maybe Int
, ou mesmo paraMaybe Double -> Either String Int
ondea :: Maybe Double; b :: Either String Int
.Portanto, a declaração vem da seguinte forma:
:: f a -> g b
(ou seja, qualquer tipo parametrizado para qualquer tipo parametrizado):: f a -> f b
(ou seja, qualquer tipo parametrizado para o mesmo tipo parametrizado) ... disse de forma diferente,Então, onde está o poder dessa construção? Para apreciar toda a dinâmica, eu precisava ver que os desenhos típicos de um monóide (objeto único com o que parece uma flecha de identidade
:: single object -> single object
) não conseguem ilustrar que tenho permissão para usar uma flecha parametrizada com qualquer número de valores de monóide, do objeto de um tipo permitido no Monoid. A definição de equivalência da seta endo, ~ identity ignora o valor do tipo do functor e o tipo e o valor da camada mais interna de "carga útil". Assim, a equivalência retornatrue
em qualquer situação em que os tipos funcionais correspondam (por exemplo,Nothing -> Just * -> Nothing
é equivalente aJust * -> Just * -> Just *
porque são os doisMaybe -> Maybe -> Maybe
).Barra lateral: ~ fora é conceitual, mas é o símbolo mais à esquerda
f a
. Também descreve o que "Haskell" lê primeiro (figura grande); então Type é "fora" em relação a um Type Value. O relacionamento entre as camadas (uma cadeia de referências) na programação não é fácil de se relacionar em Categoria. A categoria de conjunto é usada para descrever tipos (int, strings, talvez int etc.), que inclui a categoria de functor (tipos parametrizados). A cadeia de referência: Tipo de Functor, valores do Functor (elementos do conjunto desse Functor, por exemplo, Nothing, Just) e, por sua vez, tudo o que cada valor do functor aponta. Em Categoria, o relacionamento é descrito de maneira diferente, por exemplo,return :: a -> m a
é considerada uma transformação natural de um Functor para outro Functor, diferente de qualquer coisa mencionada até agora.De volta ao thread principal, no geral, para qualquer produto tensor definido e um valor neutro, a instrução acaba descrevendo uma construção computacional incrivelmente poderosa nascida de sua estrutura paradoxal:
:: List
); estáticofold
que não diz nada sobre a carga útil)Em Haskell, esclarecer a aplicabilidade da declaração é importante. O poder e a versatilidade dessa construção não têm absolutamente nada a ver com uma mônada em si . Em outras palavras, a construção não depende do que torna uma mônada única.
Ao tentar descobrir se o código deve ser construído com um contexto compartilhado para suportar cálculos que dependem um do outro, em comparação com cálculos que podem ser executados em paralelo, essa infame declaração, tanto quanto descreve, não é um contraste entre a escolha de Aplicável, Setas e Mônadas, mas é uma descrição de quanto elas são iguais. Para a decisão em mãos, a declaração é discutível.
Isso geralmente é mal compreendido. A declaração continua a descrever
join :: m (m a) -> m a
como o produto tensorial do endofuncor monoidal. No entanto, não articula como, no contexto desta declaração,(<*>)
também poderia ter sido escolhido. É realmente um exemplo de seis / meia dúzia. A lógica para combinar valores é exatamente igual; a mesma entrada gera a mesma saída de cada uma (diferente dos monoides Sum e Product para Int porque eles geram resultados diferentes ao combinar Ints).Então, para recapitular: Um monóide na categoria de endofunitores descreve:
(<*>)
e(>>=)
ambos fornecem acesso simultâneo aos doism
valores para calcular o valor de retorno único. A lógica usada para calcular o valor de retorno é exatamente a mesma. Se não fossem as diferentes formas das funções que eles parametrizam (f :: a -> b
versusk :: a -> m b
) e a posição do parâmetro com o mesmo tipo de retorno da computação (ou seja,a -> b -> b
versusb -> a -> b
para cada um respectivamente), suspeito que poderíamos ter parametrizado a lógica monoidal, o produto tensorial, para reutilização em ambas as definições. Como um exercício para explicar, tente e implemente~t
, e você termina(<*>)
e(>>=)
depende de como decide defini-loforall a b
.Se meu último ponto é no mínimo conceitualmente verdadeiro, ele explica a diferença precisa e única computacional entre Aplicável e Mônada: as funções que eles parametrizam. Em outras palavras, a diferença é externa à implementação dessas classes de tipos.
Concluindo, em minha própria experiência, a citação infame de Mac Lane forneceu um ótimo meme "goto", um guia para eu fazer referência enquanto navegava na Categoria para entender melhor os idiomas usados em Haskell. Ele consegue capturar o escopo de uma poderosa capacidade de computação tornada maravilhosamente acessível em Haskell.
No entanto, há uma ironia em como eu entendi errado a aplicabilidade da declaração fora da mônada, e o que espero transmitir aqui. Tudo o que ele descreve acaba sendo o mesmo entre Aplicável e Mônadas (e Flechas, entre outros). O que não diz é precisamente a pequena mas útil distinção entre eles.
- E
fonte
As respostas aqui fazem um excelente trabalho na definição de monóides e mônadas, no entanto, elas ainda não parecem responder à pergunta:
O cerne da questão que falta aqui é a noção diferente de "monóide", a chamada categorização mais precisamente - a de monóide em uma categoria monoidal. Infelizmente, o próprio livro de Mac Lane o torna muito confuso :
Confusão principal
Por que isso é confuso? Porque não define o que é "monóide na categoria de endofunitores" de
X
. Em vez disso, essa sentença sugere levar um monóide dentro do conjunto de todos os endofunitores junto com a composição do functor como operação binária e o functor de identidade como uma unidade monoidal. O que funciona perfeitamente bem e transforma em monóide qualquer subconjunto de endofunitores que contém o functor de identidade e é fechado na composição do functor.No entanto, essa não é a interpretação correta, que o livro deixa de esclarecer nessa fase. Uma Mônada
f
é um endofuncor fixo , não um subconjunto de endofuncores fechado sob composição. Uma construção comum é a utilizaçãof
para gerar uma monóide, tendo o conjunto de todas ask
composições vezes def^k = f(f(...))
def
com ele mesmo, incluindok=0
que corresponde à identidadef^0 = id
. E agora o conjuntoS
de todos esses poderes para todosk>=0
é de fato um monóide "com o produto × substituído pela composição dos endofunitores e pela unidade definida pelo endofuncor de identidade".E ainda:
S
pode ser definido para qualquer functorf
ou mesmo literalmente para qualquer auto-mapa deX
. É o monóide gerado porf
.S
dada pela composição do functor e pelo functor de identidade não tem nada a ver comf
ser ou não ser uma mônada.E para tornar as coisas mais confusas, a definição de "monóide na categoria monoidal" vem mais adiante neste livro, como você pode ver no índice . E, no entanto, entender essa noção é absolutamente crítico para entender a conexão com as mônadas.
Categorias monoidais (estritas)
Indo para o capítulo VII sobre monóides (que vem depois do capítulo VI sobre mônadas), encontramos a definição da chamada categoria monoidal estrita como tripla
(B, *, e)
, ondeB
é uma categoria,*: B x B-> B
um bifunctor (functor em relação a cada componente com outro componente fixo) ) ee
é um objeto de unidade queB
satisfaz as leis de associatividade e de unidade:para quaisquer objetos
a,b,c
deB
, e as mesmas identidades para qualquer morphismsa,b,c
come
substituídas porid_e
, o morfismo identidadee
. Agora é instrutivo observar que, no nosso caso de interesse, ondeB
está a categoria de endofundeirosX
com transformações naturais como morfismos,*
composição de functor ee
functor de identidade, todas essas leis são cumpridas, como podem ser verificadas diretamente.O que vem depois no livro é a definição da categoria monoidal "relaxada" , em que as leis mantêm apenas algumas transformações naturais fixas que satisfazem as chamadas relações de coerência , o que, no entanto, não é importante para os nossos casos das categorias de endofuncores.
Monoides em categorias monoidais
Finalmente, na seção 3 "Monoids" do capítulo VII, a definição real é dada:
fazendo 3 diagramas comutativos. Lembre-se que, no nosso caso, estes são morphisms na categoria de endofunctors, que são transformações naturais correspondentes a precisão
join
ereturn
para uma mônada. A conexão se torna ainda mais clara quando tornamos a composição*
mais explícita, substituindoc * c
porc^2
ondec
está nossa mônada.Finalmente, observe que os três diagramas comutativos (na definição de um monóide na categoria monoidal) são escritos para categorias monoidais gerais (não estritas), enquanto no nosso caso todas as transformações naturais que surgem como parte da categoria monoidal são na verdade identidades. Isso tornará os diagramas exatamente iguais aos da definição de mônada, completando a correspondência.
Conclusão
Em resumo, qualquer mônada é, por definição, um endofuncor, portanto, um objeto na categoria de endofunitores, em que o monádico
join
e osreturn
operadores satisfazem a definição de um monóide nessa categoria monoidal (estrita) específica . Vice-versa, qualquer monóide na categoria monoidal de endofuncores é, por definição, um triplo que(c, mu, nu)
consiste em um objeto e duas setas, por exemplo, transformações naturais em nosso caso, que cumprem as mesmas leis que uma mônada.Finalmente, observe a principal diferença entre os monóides (clássicos) e os monóides mais gerais nas categorias monoidais. As duas setas
mu
enu
acima não são mais uma operação binária e uma unidade em um conjunto. Em vez disso, você tem um endofunctor fixoc
. A composição do functor*
e o functor de identidade por si só não fornecem a estrutura completa necessária para a mônada, apesar dessa observação confusa no livro.Outra abordagem seria para comparar com o monoid padrão
C
de todas as auto-aplicações de um conjuntoA
, onde a operação binária é a composição, que pode ser visto para mapear o produto cartesiano padrãoC x C
emC
. Passando para o monóide categorizado, estamos substituindo o produto cartesianox
pela composição de functor*
e a operação binária é substituída pela transformação naturalmu
dec * c
parac
, que é uma coleção dejoin
operadorespara cada objeto
T
(digite na programação). E os elementos de identidade nos monoides clássicos, que podem ser identificados com imagens de mapas de um conjunto fixo de um ponto, são substituídos pela coleção dosreturn
operadoresMas agora não há mais produtos cartesianos, portanto não há pares de elementos e, portanto, nenhuma operação binária.
fonte