2017-07-18 12 views
3

J'essaie d'apprendre Haskell. J'essaye d'écrire un programme qui contient un "état global": Vars. Je veux changer une composante de l'état (par exemple var1) chaque fois que j'appelle une fonction. Le changement peut être une simple fonction sur les composants (par exemple +4). En outre, il imprime le composant modifié. Voici ce que j'ai fait jusqu'ici (mais je suis coincé). Edit: après avoir exécuté le code, je veux voir la version récente de l'état global.Haskell: Transformateurs Monad et état global

import Control.Monad.State 
import Control.Monad.IO.Class (liftIO) 

data Vars = Vars { 
var1 :: Int, 
var2 :: Float 
} deriving (Show) 

sample :: StateT Vars IO a 
sample = do 
     a <- change 
     liftIO $ print a 
     -- I want to call change again and apply more change to the state 


change :: StateT Vars IO a 
change = do 
     dd <- get 
     -- I don't know what to do next! 

main = do 
    runStateT sample (Vars 20 3) 
    evalStateT sample (Vars 20 3) 
+0

Pourriez-vous spécifier la sortie désirée du programme? Qu'est-ce que vous voulez observer après l'exécution de la fonction «main»? – Shersh

+0

@Shersh Merci pour l'indice. Je viens de le faire. – 4xx

Répondre

2

Essayons de résoudre votre problème étape par étape en commençant par les parties plus simples et plus petites. C'est une compétence importante dans la programmation et FP vous enseigne cette compétence de manière agréable. En outre, en travaillant avec State monad et surtout avec plusieurs effets dans monad-transformers vous aide à raisonner sur les effets et à mieux comprendre les choses.

  1. Vous voulez mettre à jour var1 l'intérieur de votre type de données immuables. Cela peut être fait uniquement en créant un nouvel objet. Donc, nous allons écrire cette fonction:

    plusFour :: Vars -> Vars 
    plusFour (Vars v1 v2) = Vars (v1 + 4) v2 
    

    Il existe des moyens de Haskell pour écrire cette fonction beaucoup plus court mais moins compréhensible, mais nous ne se soucient pas de ces choses maintenant.

  2. Maintenant, vous voulez utiliser cette fonction à l'intérieur State monad pour mettre à jour l'état immuable et par cette simulation de mutabilité. Que peut-on dire de cette fonction seulement en regardant sa signature de type: change :: StateT Vars IO a? Nous pouvons dire que cette fonction a plusieurs effets: elle a accès à l'état Vars et peut effectuer des actions arbitraires IO. Cette fonction renvoie également la valeur de type a. Hmm, ce dernier est étrange. Qu'est-ce que a? Qu'est-ce que cette fonction devrait renvoyer? En programmation impérative, cette fonction aura le type void ou Unit. Il suffit de faire choses, il ne retourne pas tout. Ne met à jour que le contexte. Donc, le type de résultat devrait être (). Cela peut être différent. Par exemple, nous pourrions vouloir retourner Vars après le changement. Mais c'est généralement une mauvaise approche en programmation. Cela rend cette fonction plus complexe. Après avoir compris ce que la fonction de type devrait avoir (essayez de toujours commencer par définir les types), nous pouvons l'implémenter. Nous voulons changer notre état. Il y a des fonctions qui fonctionnent avec des parties dynamiques de notre contexte. Fondamentalement, vous intéressé par celui-ci:

    modify :: Monad m => (s -> s) -> StateT s m()

    fonction modify prend fonction qui met à jour l'état. Après avoir exécuté cette fonction, vous pouvez observer que l'état est modifié en fonction de la fonction transmise. Maintenant change peut être écrit comme ceci:

    change :: StateT Vars IO() 
    change = modify plusFour 
    

    Vous pouvez implémenter modify (et donc change en utilisant seulement put et get fonctions qui est bon exercice pour les débutants).

  3. Appelons maintenant la fonction change à partir d'une autre fonction. Que signifie appeler dans ce cas? Cela signifie que vous exécutez l'action monadique change. Cette action change votre contexte, vous ne vous souciez pas de son résultat car c'est (). Mais si vous exécutez la fonction get (qui lie l'état entier à la variable) après change, vous pouvez observer de nouveaux changements.Si vous souhaitez imprimer seulement le composant modifié, comme var1 vous pouvez utiliser la fonction gets. Et, encore une fois, quel type devrait sample avoir? Que devrait-il revenir? Si du côté de l'appelant que vous êtes intéressé que dans l'état résultant, puis, encore une fois, il devrait être () comme ceci:

    sample :: StateT Vars IO() 
    sample = do 
        change 
        v1 <- gets var1 
        liftIO $ print v1 
        change 
        v1' <- gets var1 
        liftIO $ print v1' -- this should be v1 + 4 
    

Cela devrait vous ajouter une certaine compréhension de ce qui se passe. Les transformateurs Monad nécessitent un certain temps pour s'y habituer, même si c'est un outil puissant (pas parfait mais extrêmement utile). En guise de note de côté, je tiens à ajouter que ces fonctions peuvent être écrites beaucoup mieux en utilisant les modèles de conception courants de Haskell. Mais vous n'avez pas besoin de vous en préoccuper, essayez simplement de comprendre ce qui se passe ici.

+0

Merci beaucoup pour la réponse précise! Vouliez-vous dire "Lenses" par "design pattern"? – 4xx

+0

@ 4xx «Objectifs» est une bibliothèque mais elle a apporté une partie de ce modèle. Je veux dire, ce type de fonction 'change' devrait être' change :: (MonadState s m, HasVar1 s) => m() '. – Shersh

+0

pouvez-vous s'il vous plaît me référer à une source expliquant pourquoi "changement" devrait être écrit de cette façon? – 4xx