【问题标题】:Haskell - maintaining different states of global variableHaskell - 维护全局变量的不同状态
【发布时间】:2017-08-23 16:12:17
【问题描述】:

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

我发现this 详细的问题解决了类似的问题。它提出了神一样的全局变量的重要问题,这是 Haskell 中的反模式。我完全理解我的情况很相似,我正在尝试介绍这种反模式,但我不太喜欢这个答案。似乎Netwire 对我手头的任务来说有点过头了,它可以以更简单和优雅的方式完成。

我还找到了this one,但问题和答案都解决了更普遍的问题和方法,而我有具体的问题,希望有具体的解决方案。我还想要(并且在以前的问题中找不到)是通过简单的示例在理解保持可变状态方面迈出定性的一步。

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

我的问题是如何修改以下代码以适应以功能方式更改全局变量值的可能性?我是否应该丢弃所有代码,因为它代表命令式方法,并替换它遵循功能世界规则的全新parseInput?我应该用其他东西替换全局变量吗?我想我可以以某种方式使用IORef,这似乎是合适的。或 ST Monad 推荐的 this question/answer

在不过度杀伤力的情况下解决这个问题的最简单直接的步骤是什么?我知道我可能需要更好地掌握 Monads(特别是 State Monad)的概念,并且我已准备好学习它们如何帮助解决这个特定问题。但是到目前为止我读过的文章(thisthis)并没有太大帮助。我认为 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 模块)。

【问题讨论】:

    标签: haskell io state


    【解决方案1】:

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

    您可以使用StateT [Int] (InputT IO)InputT (StateT [Int] IO),而不是使用InputT IO 类型。由于InputT 有更多的操作要处理提升,我会使用InputT (StateT [Int] IO) 将复杂的操作保留在外面。

    为了简单起见,我会为 MonadState m =&gt; MonadState (InputT m) 添加一个孤儿 MonadState 实例

    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
    

    然后您可以清理类型签名以使您的代码更通用。您可以让代码为(MonadState [Int] m, MonadIO m) =&gt; InputT m 工作,而不是只在InputT (StateT [Int] IO) 上工作。

    要运行StateT,请使用runStateT。如果您将mainloop 的类型更改为InputT (StateT [Int] IO) () 或更通用的(MonadState [Int] m, MonadIO m) =&gt; InputT m (),那么您可以使用

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

    【讨论】:

    • 你从哪里导入lift函数?应该是get = lift . get 而不是get = lift get?我还将parseInput 函数的签名从String -&gt; InputT IO () 更改为(如您所建议的)String -&gt; InputT (StateT [Int] IO),但在mainLoop 函数中出现错误Couldn't match type ‘StateT [Int] IO’ with ‘IO’ Expected type: String -&gt; InputT IO () Actual type: String -&gt; InputT (StateT [Int] IO) ()。我如何重写mainLoop 以适应parseInput 类型的变化?
    • lift 来自 MonadTrans 类。 lift get是对的; get 不是函数。
    猜你喜欢
    • 2021-05-17
    • 1970-01-01
    • 2018-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-06
    • 2020-08-24
    • 1970-01-01
    相关资源
    最近更新 更多