Question

J'ai une fonction:

test :: String -> State String String
test x = 
    get >>= \test ->
    let test' = x ++ test in
    put test' >>
    get >>= \test2 -> put (test2 ++ x) >>
    return "test"

Je peux très bien comprendre ce qui se passe tout au long de cette fonction et je commence à maîtriser les monades. Ce que je ne comprends pas, c'est comment, quand j’exécute ceci:

runState (test "testy") "testtest"

la fonction 'get' dans 'test' obtient en quelque sorte l'état initial "testtest". Quelqu'un peut-il décomposer cela et l'expliquer à moi?

J'apprécie vos réponses!

Était-ce utile?

La solution

Au départ, je devais publier ce commentaire en tant que commentaire, mais j'ai décidé d'en expliquer un peu plus.

À proprement parler, get ne "prend pas". une dispute. Je pense que beaucoup de ce qui se passe est masqué par ce que vous ne voyez pas - les définitions d’instance de la monade d’État.

get est en fait une méthode de la classe MonadState. La monade d'état est une instance de MonadState, fournissant la définition suivante de get :

get = State $ \s -> (s,s)

En d'autres termes, get renvoie simplement une monade d'état très basique (rappelant qu'une monade peut être considérée comme un "wrapper" pour un calcul), où toute entrée s dans le calcul retournera une paire de s comme résultat.

La prochaine chose à regarder est > > = , quel Etat définit ainsi:

m >>= k  = State $ \s -> let
    (a, s') = runState m s
    in runState (k a) s'

Ainsi, > > = donnera un nouveau calcul, qui ne sera pas calculé tant qu'il n'aura pas un état initial (c'est le cas de tous les calculs d'état lorsqu'ils sont en leur forme "emballée"). Le résultat de ce nouveau calcul est obtenu en appliquant ce qui se trouve du côté droit du > > = au résultat de l'exécution du calcul qui était du côté gauche. (C'est une phrase assez déroutante qui peut nécessiter une lecture supplémentaire ou deux.)

J'ai trouvé cela très utile pour " desugar " tout ce qui se passe. Cela prend beaucoup plus de frappe, mais devrait apporter une réponse très claire à votre question (d'où provient obtenir ). Notez que ce qui suit devrait être considéré comme un 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...

Cela devrait indiquer que le résultat final de notre fonction est un nouveau calcul d'état qui nécessitera une valeur initiale ( s ) pour que le reste de la tâche se produise. Vous avez fourni s en tant que "testtest & " avec votre appel runState . Si vous remplacez " testtest " pour s dans le pseudocode ci-dessus, vous verrez que la première chose qui se passe est que nous exécutons get avec "quottest test". comme «état initial». Cela donne ("testtest", "testtest") et ainsi de suite.

C’est donc là que get obtient votre état initial "testtest". J'espère que cela aide!

Autres conseils

Cela vous aidera peut-être à examiner de plus près ce qu'est réellement le constructeur de type State , et comment runState l'utilise. Dans 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 prend deux arguments: le type de l'état et le type renvoyé. Il est implémenté comme une fonction prenant l’état initial et donnant une valeur de retour et le nouvel état.

runState prend une telle fonction, la saisie initiale, et (très probablement) applique simplement l'une à l'autre pour récupérer la paire (résultat, état).

Votre fonction test est une grande composition de fonctions de type State , chacune prenant une entrée d'état et produisant une sortie (résultat, état), enfichée l'une dans l'autre. une manière qui a du sens pour votre programme. Tout ce que runState leur fournit, c'est un point de départ pour leur état.

Dans ce contexte, get est simplement une fonction qui prend un état en entrée et renvoie une sortie (résultat, état) telle que le résultat soit l'état d'entrée et que l'état soit inchangé ( l'état de sortie est l'état d'entrée). En d'autres termes, get s = (s, s)

Parcourez le chapitre 8 ("Analyseurs fonctionnels") de Programmer en Haskell plusieurs fois jusqu'à ce que je comprenne bien, suivi du didacticiel Tout à propos de Monads , a fait ce clic pour moi.

Le problème avec les monades est qu’elles sont très utiles pour plusieurs choses que nous trouvons assez différentes de ceux qui viennent de la programmation habituelle. Il faut un certain temps pour comprendre que le flux de contrôle et l’état de traitement sont non seulement assez similaires pour pouvoir être traités par le même mécanisme, mais qu’ils sont également identiques lorsque vous reculez suffisamment.

Une épiphanie est arrivée lorsque j’envisageais des structures de contrôle en C ( pour et tandis que , etc.) et j’ai réalisé que la structure de contrôle de loin la plus courante était simplement: mettre une déclaration avant l'autre. Il a fallu un an d’étude de Haskell pour comprendre que c’était même une structure de contrôle.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top