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

标签 haskell io state

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

我找到了this解决类似问题的详细问题。它提出了神圣全局变量的重要问题,这是 Haskell 中的反模式。我完全理解我的情况相似,我正在尝试引入这种反模式,但我不太喜欢这个答案。看来 Netwire 对于我手头的任务来说有点大材小用了,它可以用更简单和优雅的方式来完成。

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

在下面的代码中,我尝试从执行 :load:new 命令的两个不同位置更新类似上帝的变量的状态,但是,显然,它没有'不工作。

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

解决这个问题最简单直接的步骤是什么?我知道我可能需要更好地掌握 Monad(特别是 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 模块) .

最佳答案

处理此问题的一种简洁方法是添加 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

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

要运行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 []

关于Haskell - 维护全局变量的不同状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45844720/

相关文章:

haskell - Haskell错误处理,将无效的文件路径记录到控制台

haskell - 将可能性添加到 Haskell 数据类型(使用记录语法)

javascript - `console.log` 一个 mobx `@observable` 每当它的值改变时

java - 需要帮助 : input int from console and pass it into method in different class

c - 在c中使用fscanf()读取多行

机器人:媒体记录器:启动失败:-38

reactjs - 如何确保reactjs状态更新,然后调用函数?

haskell - Haskell 中是否有索引列表,它是好是坏?

Haskell 模式匹配 - 多个条件与一个表达式匹配

node.js - 为什么 Node 会产生多个线程?