Haskell - 维护全局变量的不同状态

alt*_*ern 2 io state haskell

我已经对stackoverflow做了一些研究,以找到维持全局变量的不同状态的常见问题的可行解决方案.

我发现这个复杂的问题解决了类似的问题.它提出了类似神的全局变量的重要问题,这是Haskell中的反模式.我完全理解我的情况是类似的,我正在尝试介绍这个反模式,但我真的不喜欢这个答案.Netwire对于我手边的任务来说,这似乎是一种矫枉过正,它可以以更加简单和优雅的方式完成.

我也找到了这个,但问题和答案都解决了更普遍的问题和方法,而我有具体的问题,希望是具体的解决方案.我想要的(在以前的问题中找不到)是通过简单的例子在理解维持变量状态方面做出定性的步骤.

在下面的代码中,我试图从执行:load:new命令的两个不同位置更新神似变量的状态,但显然,它不起作用.

我的问题是如何修改以下代码以适应以功能方式更改全局变量值的可能性?我是否应该抛弃所有代码,因为它代表了命令式的方法,并将其全部替换为parseInput遵循功能世界规则的新方法?我应该用其他东西替换全局变量吗?我假设我可以用IORef某种方式,这似乎是合适的.或ST Monad这个问题/答案建议.

如果没有过度杀伤力,解决这个问题的最简单,最直接的步骤是什么?我知道我可能需要更好地掌握Monads(特别是State Monad)的概念,我准备好了解他们如何帮助解决这个特殊问题.但到目前为止我读过的文章(这个这个)并没有多大帮助.我假设State Monad不合适,因为我的例子没有返回值,只有更新状态.如果我错了,你能否解释一下如何以及哪些缺失链接可以帮助我更好地理解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)
Run Code Online (Sandbox Code Playgroud)

PS.我从这个答案中使用了模板Haskell定义(TH模块).

Cir*_*dec 5

处理此问题的一种简洁方法是添加StateT到变压器堆栈中.

而不是使用InputT IO您使用的类型StateT [Int] (InputT IO)InputT (StateT [Int] IO).因为InputT有更多的操作来解除提升,我会InputT (StateT [Int] IO)用来保持外面的复杂操作.

为简单起见,我要添加一个孤儿MonadState实例MonadState m => MonadState (InputT m)

instance MonadState s m => MonadState s (InputT m) where
    get = lift get
    put = lift . put
    state = lift . state
Run Code Online (Sandbox Code Playgroud)

然后,当你要修改的状态,你会使用get,putstate.

  | inp =~ "^\\:new" = do
    mydata <- get                     -- reads the state
    put $ mydata ++ [last mydata + 1] -- updates the state
    mainLoop
Run Code Online (Sandbox Code Playgroud)

然后,您可以清理类型签名以使代码更通用.而不仅仅是InputT (StateT [Int] IO)你可以使代码工作(MonadState [Int] m, MonadIO m) => InputT m.

运行StateT使用runStateT.如果您更改mainloopInputT (StateT [Int] IO) ()或更通用的类型,(MonadState [Int] m, MonadIO m) => InputT m ()则可以使用它来运行它

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