2 votes

Haskell - maintenir différents états d'une variable globale

J'ai fait quelques recherches sur stackoverflow pour trouver une solution viable au problème courant du maintien de différents états d'une variable globale.

J'ai trouvé este une question élaborée qui répond à une préoccupation similaire. Elle soulève un problème important, celui des variables globales de type "godlike", ce qui est un anti-modèle en Haskell. Je comprends parfaitement que ma situation est similaire et que j'essaie d'introduire cet anti-modèle, mais je n'aime pas vraiment la réponse. Il semble que Netwire est exagéré pour la tâche qui m'incombe, il pourrait être réalisé de manière beaucoup plus simple et élégante.

J'ai également trouvé celui-ci Mais les questions et les réponses portent sur des préoccupations et des approches plus générales, alors que j'ai un problème concret et, je l'espère, une solution concrète. Ce que je souhaite également (et que je n'ai pas trouvé dans les questions précédentes), c'est faire un pas qualitatif dans la compréhension du maintien des états des variables à travers un exemple simple.

Dans le code ci-dessous, j'essaie de mettre à jour l'état de la variable Godlike à partir de deux endroits différents en exécutant :load y :new mais, manifestement, cela ne fonctionne pas.

Ma question est la suivante : comment modifier le code suivant afin de tenir compte de la possibilité de modifier la valeur de la variable globale de manière fonctionnelle ? Dois-je jeter tout le code parce qu'il représente une approche de type impératif et le remplacer par un code totalement nouveau ? parseInput qui suit les règles du monde fonctionnel ? Dois-je remplacer la variable globale par quelque chose d'autre ? Je suppose que je pourrais utiliser IORef D'une manière ou d'une autre, cela semble approprié. Ou bien ST Monad comme cette question/réponse recommandent.

Quelle serait la mesure la plus simple et la plus directe pour résoudre ce problème sans excès ? Je comprends que je puisse avoir besoin de mieux saisir la notion de Monades (State Monad en particulier) et je suis prêt à apprendre comment elles pourraient m'aider à résoudre ce problème particulier. Mais les articles que j'ai lus jusqu'à présent ( este y este ), n'a pas été d'un grand secours. Je suppose que la monade d'état n'est pas vraiment appropriée parce que mon exemple n'a pas de valeur de retour, seulement un état mis à jour. Si je me trompe, pourriez-vous m'expliquer comment et quels liens manquants m'aideraient à mieux comprendre les états en Haskell ?

{-# LANGUAGE QuasiQuotes #-}

import Text.Regex.PCRE
import System.Console.Haskeline
import TH (litFile)
import System.FilePath
import System.IO
import Control.Monad
import Control.Monad.IO.Class
import Data.List 

mydata :: [Int]
mydata = [0]

saveDataToFile :: [Int] -> IO ()
saveDataToFile mydata = withFile "data.txt" WriteMode $ \h -> System.IO.hPutStr h (unwords $ map show mydata)

loadDataFromFile :: [Int]
loadDataFromFile = map read . words $ [litFile|data.txt|]

help :: InputT IO ()
help = liftIO $ mapM_ putStrLn
       [ ""
       , ":help     - this help"
       , ":q        - quit"
       , ":commands - list available commands"
       , ""
       ]

commands :: InputT IO ()
commands = liftIO $ mapM_ putStrLn
       [ ""
       , ":show     - display data"
       , ":save     - save results to file"
       , ":load     - loads data from file"
       , ":new      - generate new element "
       , ""
       ]

parseInput :: String -> InputT IO ()
parseInput inp
  | inp =~ "^\\:q"        = return ()

  | inp =~ "^\\:he"       = help >> mainLoop

  | inp =~ "^\\:commands" = commands >> mainLoop

  | inp =~ "^\\:show" = do
    liftIO $ putStrLn $ unwords $ map show mydata
    mainLoop 

  | inp =~ "^\\:save" = do
    liftIO $ saveDataToFile mydata
    mainLoop

  | inp =~ "^\\:load" = do
    let mydata = loadDataFromFile -- <-- should update mydata 
    mainLoop

  | inp =~ "^\\:new" = do
    let mydata = mydata ++ [last mydata + 1] -- <-- should update mydata
    mainLoop

  | inp =~ ":" = do
    outputStrLn $ "\nNo command \"" ++ inp ++ "\"\n"
    mainLoop

  | otherwise = handleInput inp

handleInput :: String -> InputT IO ()
handleInput inp = mainLoop

mainLoop :: InputT IO ()
mainLoop = do
  inp <- getInputLine "% "
  maybe (return ()) (parseInput) inp

greet :: IO ()
greet = mapM_ putStrLn
        [ ""
        , "          MyProgram"
        , "=============================="
        , "For help type \":help\""
        , ""
        ]

main :: IO ()
main = do 
    greet 
    runInputT defaultSettings (mainLoop)

PS. J'utilise les définitions Template Haskell (module TH) de cette réponse .

5voto

Cirdec Points 5230

Une façon propre de gérer cela est d'ajouter StateT à votre pile de transformateurs.

Au lieu d'utiliser le type InputT IO vous utiliserez soit StateT [Int] (InputT IO) ou InputT (StateT [Int] IO) . Depuis le InputT a plus d'opérations à effectuer pour lever des fonds, j'utiliserais InputT (StateT [Int] IO) pour que les opérations compliquées restent à l'extérieur.

Pour simplifier les choses, j'ajouterais un orphelin MonadState instance pour MonadState m => MonadState (InputT m)

instance MonadState s m => MonadState s (InputT m) where
    get = lift get
    put = lift . put
    state = lift . state

Ensuite, lorsque vous souhaitez modifier l'état, vous devez utiliser get , put ou state .

  | inp =~ "^\\:new" = do
    mydata <- get                     -- reads the state
    put $ mydata ++ [last mydata + 1] -- updates the state
    mainLoop

Vous pouvez ensuite nettoyer les signatures de type pour rendre votre code plus général. Au lieu de travailler uniquement sur InputT (StateT [Int] IO) vous pouvez faire fonctionner le code pour (MonadState [Int] m, MonadIO m) => InputT m .

Pour exécuter un StateT utiliser runStateT . Si vous changez le type de mainloop a InputT (StateT [Int] IO) () ou le plus général (MonadState [Int] m, MonadIO m) => InputT m () vous pouvez alors l'exécuter avec

main :: IO ()
main = do 
    greet 
    runStateT (runInputT defaultSettings mainLoop) []
--  ^          ^ run the outer InputT              ^
--  run the inner StateT ..... with starting state []

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X