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



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

我发现了这个复杂的问题,解决了类似的问题。它提出了上帝类全局变量的重要问题,这是哈斯克尔的一个反模式。我完全理解我的情况是相似的,我试图引入这种反模式,但我真的不喜欢这个答案。看来Netwire对于我手头的任务来说有点矫枉过正,但它可以用更简单和优雅的方式完成。

我也发现了这个问题,但问题和答案都涉及更普遍的问题和方法,而我有具体的问题,希望是具体的解决方案。我还想要(在前面的问题中找不到(是通过简单的例子在理解维护变量状态方面迈出定性的一步。

在下面的代码中,我尝试从两个不同的地方更新 godlike 变量的状态,执行:load:new命令,但显然,它不起作用。

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

在不矫枉过正的情况下解决此问题的最简单直接的步骤是什么?我知道我可能需要更好地掌握Monads(特别是国家Monad(的概念,我准备了解他们如何帮助解决这个特定问题。但是到目前为止我读过的文章(这个和这个(并没有多大帮助。我假设状态 Monad 并不真正合适,因为我的示例没有返回值,只有更新的状态。如果我错了,您能否解释一下如何以及哪些缺失的链接可以帮助我更好地理解哈斯克尔的状态?

{-# 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)

附言。我使用这个答案中的模板 Haskell 定义(TH 模块(。

处理此问题的一种干净方法是将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

然后,当您要修改状态时,可以使用getputstate

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

然后,可以清理类型签名以使代码更通用。您不仅可以处理InputT (StateT [Int] IO)还可以使代码适用于(MonadState [Int] m, MonadIO m) => InputT m

要运行StateT请使用runStateT。如果将mainloop类型更改为InputT (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 []

最新更新