我已经对stackoverflow做了一些研究,以找到维持全局变量的不同状态的常见问题的可行解决方案。
我发现了this详尽的问题,解决了类似问题。它提出了类似神的全局变量的重要问题,这是Haskell中的反模式。我完全理解我的情况是类似的,我正在尝试介绍这个反模式,但我真的不喜欢这个答案。似乎Netwire
对我手头的任务来说太过分了,它可以以更加简单和优雅的方式完成。
我还发现this one,但问题和答案都涉及更普遍的问题和方法,而我有具体的问题,希望是具体的解决方案。我想要的(在以前的问题中找不到)是通过简单的例子来理解维持变量状态的定性步骤。
在下面的代码中,我试图从执行:load
和:new
命令的两个不同位置更新神似变量的状态,但显然,它不起作用。
我的问题是如何修改以下代码以适应以功能方式更改全局变量值的可能性?我是否应该抛弃所有代码,因为它代表了命令式的方法,并将其全部替换为遵循功能世界规则的新parseInput
?我应该用其他东西替换全局变量吗?我假设我可以以某种方式使用IORef
,这似乎是合适的。或ST Monad
为this question/answer推荐。
如果没有过度杀伤力,解决这个问题的最简单明了的步骤是什么?我知道我可能需要更好地掌握Monads(特别是State Monad)的概念,我准备好了解他们如何帮助解决这个特殊问题。但到目前为止我读过的文章(this和this)并没有多大帮助。我假设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)
PS。我使用来自this answer的模板Haskell定义(TH模块)。
答案 0 :(得分:5)
处理此问题的一种简洁方法是将StateT
添加到变换器堆栈中。
不使用InputT IO
类型,而是使用StateT [Int] (InputT IO)
或InputT (StateT [Int] IO)
。由于InputT
有更多操作可以解除提升,我会使用InputT (StateT [Int] IO)
将复杂的操作保留在外面。
为简单起见,我要为MonadState m => MonadState (InputT m)
MonadState
个实例
instance MonadState s m => MonadState s (InputT m) where
get = lift get
put = lift . put
state = lift . state
然后,当您想要修改状态时,请使用get
,put
或state
。
| 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 []