A mônada do leitor é tão complexa e parece inútil. Em uma linguagem imperativa como Java ou C ++, não existe um conceito equivalente para a mônada do leitor, se não me engano.
Você pode me dar um exemplo simples e esclarecer um pouco isso?
haskell
monads
reader-monad
chipbk10
fonte
fonte
Respostas:
Não tenha medo! A mônada do leitor não é tão complicada e tem uma utilidade muito fácil de usar.
Existem duas maneiras de abordar uma mônada: podemos perguntar
Desde a primeira abordagem, a mônada do leitor é algum tipo abstrato
de tal modo que
Então, como usamos isso? Bem, a mônada do leitor é boa para passar informações de configuração (implícitas) por meio de um cálculo.
Sempre que você tem uma "constante" em um cálculo que você precisa em vários pontos, mas realmente gostaria de ser capaz de realizar o mesmo cálculo com valores diferentes, você deve usar um leitor de mônada.
As mônadas de leitor também são usadas para fazer o que as pessoas OO chamam de injeção de dependência . Por exemplo, o algoritmo negamax é usado com frequência (em formas altamente otimizadas) para calcular o valor de uma posição em um jogo para dois jogadores. O algoritmo em si, entretanto, não se importa com qual jogo você está jogando, exceto que você precisa ser capaz de determinar quais são as "próximas" posições no jogo, e você precisa ser capaz de dizer se a posição atual é uma posição de vitória.
Isso funcionará com qualquer jogo finito e determinístico para dois jogadores.
Esse padrão é útil mesmo para coisas que não são realmente injeção de dependência. Suponha que você trabalhe com finanças, possa criar uma lógica complicada para precificar um ativo (um derivado, digamos), o que é muito bom e você pode fazer sem nenhuma mônada fedorenta. Mas então, você modifica seu programa para lidar com várias moedas. Você precisa ser capaz de converter moedas rapidamente. Sua primeira tentativa é definir uma função de nível superior
para obter preços spot. Você pode então chamar este dicionário em seu código .... mas espere! Isso não vai funcionar! O dicionário de moeda é imutável e, portanto, deve ser o mesmo não apenas durante a vida do seu programa, mas desde o momento em que ele é compilado ! Então, o que você faz? Bem, uma opção seria usar a mônada do Reader:
Talvez o caso de uso mais clássico seja na implementação de interpretadores. Mas, antes de olharmos para isso, precisamos apresentar outra função
Ok, então Haskell e outras linguagens funcionais são baseadas no cálculo lambda . O cálculo lambda tem uma sintaxe que parece
e queremos escrever um avaliador para este idioma. Para fazer isso, precisaremos manter o controle de um ambiente, que é uma lista de associações associadas a termos (na verdade, serão encerramentos porque queremos fazer escopo estático).
Quando terminarmos, devemos obter um valor (ou um erro):
Então, vamos escrever o intérprete:
Finalmente, podemos usá-lo passando um ambiente trivial:
E é isso. Um intérprete totalmente funcional para o cálculo lambda.
A outra maneira de pensar sobre isso é perguntando: como isso é implementado? A resposta é que a mônada do leitor é, na verdade, uma das mais simples e elegantes de todas as mônadas.
Leitor é apenas um nome sofisticado para funções! Já definimos,
runReader
e quanto às outras partes da API? Bem, todoMonad
também é umFunctor
:Agora, para obter uma mônada:
o que não é tão assustador.
ask
é muito simples:enquanto
local
não é tão ruim:Ok, então a mônada do leitor é apenas uma função. Por que ter o Reader? Boa pergunta. Na verdade, você não precisa disso!
Estes são ainda mais simples. Além disso,
ask
é justoid
elocal
é apenas composição de funções com a ordem das funções trocadas!fonte
Reader
é uma função com alguma implementação particular da classe do tipo mônada? Dizer isso antes teria me ajudado a ficar um pouco menos confuso. Primeiro eu não estava entendendo. No meio do caminho, pensei "Oh, isso permite que você retorne algo que dará o resultado desejado, uma vez que você forneça o valor ausente." Achei isso útil, mas de repente percebi que uma função faz exatamente isso.local
função precisa de mais explicações.(Reader f) >>= g = (g (f x))
?x
?Lembro-me de ter ficado intrigado como você, até que descobri por conta própria que variantes da mônada do Reader estão por toda parte . Como eu descobri isso? Porque eu continuei escrevendo código que acabou sendo pequenas variações dele.
Por exemplo, em um ponto eu estava escrevendo algum código para lidar com valores históricos ; valores que mudam com o tempo. Um modelo muito simples disso é funções de pontos de tempo para o valor naquele ponto no tempo:
A
Applicative
instância significa que se você tememployees :: History Day [Person]
ecustomers :: History Day [Person]
pode fazer isso:Ou seja,
Functor
eApplicative
nos permitem adaptar funções regulares, não-históricas para trabalhar com histórias.A instância da mônada é mais intuitivamente entendida considerando a função
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
. Uma função de tipoa -> History t b
é uma função que mapeia uma
para um histórico deb
valores; por exemplo, você poderia tergetSupervisor :: Person -> History Day Supervisor
, egetVP :: Supervisor -> History Day VP
. Portanto, a instância de Monad paraHistory
trata de funções de composição como essas; por exemplo,getSupervisor >=> getVP :: Person -> History Day VP
é a função que obtém, para qualquerPerson
, o histórico deVP
s que eles tiveram.Bem, esta
History
mônada é exatamente igual aReader
.History t a
é realmente o mesmo queReader t a
(que é o mesmo quet -> a
).Outro exemplo: tenho feito protótipos de designs OLAP em Haskell recentemente. Uma ideia aqui é a de um "hipercubo", que é um mapeamento de interseções de um conjunto de dimensões para valores. Aqui vamos nós novamente:
Uma operação comum em hipercubos é aplicar funções escalares de vários locais aos pontos correspondentes de um hipercubo. Podemos obter isso definindo uma
Applicative
instância paraHypercube
:Acabei de copiar o
History
código acima e mudar os nomes. Como você pode perceber,Hypercube
também é justoReader
.Isso continua e continua. Por exemplo, os intérpretes de linguagem também se resumem a
Reader
, quando você aplica este modelo:Reader
ask
Reader
ambiente de execução.local
Uma boa analogia é que a
Reader r a
representa uma
com "buracos" que o impedem de saber do quea
estamos falando. Você só pode obter um reala
depois de fornecer umr
para preencher os buracos. Existem toneladas de coisas assim. Nos exemplos acima, um "histórico" é um valor que não pode ser calculado até que você especifique um tempo, um hipercubo é um valor que não pode ser calculado até que você especifique uma interseção e uma expressão de linguagem é um valor que pode não deve ser calculado até que você forneça os valores das variáveis. Também lhe dá uma intuição sobre por queReader r a
é o mesmo quer -> a
, porque essa função também é intuitivamente um ana
ausenter
.Portanto
Functor
, as instânciasApplicative
eMonad
deReader
são uma generalização muito útil para casos em que você está modelando qualquer coisa do tipo "uma
que está faltando umr
" e permite que você trate esses objetos "incompletos" como se fossem completos.Contudo uma outra maneira de dizer a mesma coisa: um
Reader r a
é algo que consomer
e produza
, eoFunctor
,Applicative
eMonad
exemplos são padrões básicos para trabalhar comReader
s.Functor
= fazer umReader
que modifica a saída de outroReader
;Applicative
= conecte doisReader
s à mesma entrada e combine suas saídas;Monad
= inspecionar o resultado de aReader
e usá-lo para construir outroReader
. As funçõeslocal
ewithReader
= fazem umReader
que modifica a entrada para outraReader
.fonte
GeneralizedNewtypeDeriving
extensão para derivarFunctor
,Applicative
,Monad
, etc. para Newtypes com base em seus tipos subjacentes.Em Java ou C ++ você pode acessar qualquer variável de qualquer lugar sem nenhum problema. Os problemas aparecem quando seu código se torna multi-thread.
Em Haskell, você tem apenas duas maneiras de passar o valor de uma função para outra:
fn1 -> fn2 -> fn3
funçãofn2
pode não precisar do parâmetro que você passa defn1
parafn3
.A mônada do Reader apenas passa os dados que você deseja compartilhar entre as funções. As funções podem ler esses dados, mas não podem alterá-los. Isso é tudo que faz a mônada do leitor. Bem, quase todos. Existem também várias funções como
local
, mas pela primeira vez você pode ficar comasks
apenas.fonte
do
anotação, o que seria melhor se fosse refatorado em uma função pura.where
cláusula, ela será aceita como uma 3ª forma de passar variáveis?