Como é que 'get' realmente / obter / o estado inicial em Haskell?
-
10-07-2019 - |
Pergunta
Eu tenho uma função:
test :: String -> State String String
test x =
get >>= \test ->
let test' = x ++ test in
put test' >>
get >>= \test2 -> put (test2 ++ x) >>
return "test"
Eu posso muito bem compreender o que se passa ao longo desta função, e eu estou começando a pegar o jeito de mônadas. O que eu não entendo é como, quando eu executar este:
runState (test "testy") "testtest"
a função 'get' em 'teste' de alguma forma obtém o estado inicial "testtest". Alguém pode decompô-lo e explicar-me?
Eu aprecio todas as respostas!
Solução
Eu estava originalmente indo para postar isso como um comentário, mas decidiu expor um pouco mais.
A rigor, get
não "tomar" um argumento. Eu acho que muito do que está acontecendo é mascarado por aquilo que você não está vendo - as definições de instância da mônada State
get
é na verdade um método da classe MonadState. A Mônada Estado é uma instância de MonadState, proporcionando a seguinte definição de get
:
get = State $ \s -> (s,s)
Em outras palavras, get
apenas retorna uma mônada muito básico Estado (lembrando que uma mônada pode ser pensado como um "wrapper" para um cálculo), onde qualquer s
entrada para o cálculo irá retornar um par de s
como resultado .
A próxima coisa que precisamos é olhar para >>=
, o Estado define assim:
m >>= k = State $ \s -> let
(a, s') = runState m s
in runState (k a) s'
Assim, >>=
vai produzir um novo cálculo, que não serão computados até que ele recebe um estado inicial (isto é verdade para todos os cálculos de Estado quando estão em sua forma "embrulhado"). O resultado deste novo cálculo é conseguido através da aplicação de tudo o que está no lado direito da >>=
para o resultado da execução do cálculo que estava do lado esquerdo. (Isso é uma frase bastante confuso que pode exigir uma leitura adicional ou dois.)
Eu achei bastante útil para tudo "desugar" que está acontecendo. Se o fizer, preciso muito mais de digitação, mas deve fazer a resposta à sua pergunta (onde get
está recebendo a partir de) muito clara. Note-se que o seguinte deve ser considerado psuedocode ...
test x =
State $ \s -> let
(a,s') = runState (State (\s -> (s,s))) s --substituting above defn. of 'get'
in runState (rightSide a) s'
where
rightSide test =
let test' = x ++ test in
State $ \s2 -> let
(a2, s2') = runState (State $ \_ -> ((), test')) s2 -- defn. of 'put'
in runState (rightSide2 a2) s2'
rightSide2 _ =
-- etc...
Isso deve tornar óbvio que o resultado final da nossa função é um novo cálculo Estado que terá um valor inicial (s
) para fazer o resto das coisas acontecer. Você forneceu s
como "testtest"
com a sua chamada runState
. Se você substituir "testtest" para s
no pseudo-código acima, você verá que a primeira coisa que acontece é que corremos get
com "testtest" como o 'estado inicial'. Este rendimentos ("testtest", "testtest")
e assim por diante.
Assim que é onde get
recebe o seu estado inicial "testtest". Espero que isso ajude!
Outras dicas
Pode ajudá-lo a tomar um olhar mais profundo que o tipo de construtor State
realmente é, e como RunState usa-lo. Em GHCi:
Prelude Control.Monad.State> :i State
newtype State s a = State {runState :: s -> (a, s)}
Prelude Control.Monad.State> :t runState
runState :: State s a -> s -> (a, s)
State
leva dois argumentos: o tipo do estado, e o tipo retornado. Ele é implementado como uma função de tomar o estado inicial e produzindo um valor de retorno e o novo estado.
runState
leva função tal, a entrada inicial, e (provavelmente) apenas aplica um para o outro para recuperar o par (resultado, estado).
A sua função test
é uma grande composição de funções do tipo State
, cada um tomando uma entrada de estado e gerando um (resultado, estado) de saída, ligado um ao outro de uma maneira que faz sentido para o seu programa. Todos runState
faz é fornecer-lhes um ponto de partida estado.
Neste contexto, get
é simplesmente uma função que leva estado como uma entrada, e retorna um (resultado, estado) de saída de tal modo que o resultado é o estado de entrada, e o estado é inalterada (o estado de saída é o estado de entrada ). Em outras palavras, get s = (s, s)
Indo até o capítulo 8 ( "Analisadores funcionais") de Programação de Graham Hutton em Haskell várias vezes até que eu entendi corretamente, seguido por um ir para o Tudo sobre Monads , feito isso clique para mim.
O problema com monads é que eles são muito úteis para várias coisas que aqueles de nós que vem do fundo de programação habitual encontrar bastante diferentes. Leva algum tempo para perceber que o fluxo de controle e manipulação de estado são suficientes não só semelhantes que podem ser tratadas pelo mesmo mecanismo, mas é quando você voltar longe o suficiente, a mesma coisa.
Uma epifania veio quando eu estava pensando em estruturas de controle em C (for
e while
, etc.), e eu percebi que, de longe, a estrutura de controle mais comum foi simplesmente colocar uma declaração perante o outro. Demorou um ano de estudo Haskell antes de eu percebi que mesmo que era uma estrutura de controle.