【问题标题】:Maintaining state during interactive command line prompt在交互式命令行提示符期间保持状态
【发布时间】:2021-01-19 03:36:55
【问题描述】:

我想编写一个具有交互式提示并且可以保存和显示所有以前输入的玩具程序。这是我的第一次尝试,但没有编译(使用 ghc):

import System.IO
import Control.Monad.State

data ProgramState = ProgramState
    { events :: [Int] }     -- Placeholder for now

parse_input :: String -> State ProgramState Bool
parse_input prompt = do
    putStr prompt
    hFlush stdout
    current_state <- get
    str <- getLine
    case str of
        "c" -> do 
            put (current_state { events = [1,2,3] } )   -- this should become actual appending
            return True
        "l" -> return True    
        "q" -> return False  
        "quit" -> return False
        "h" -> return True
        _ -> do 
            putStrLn "Invalid input."
            parse_input prompt

main :: IO ()
main = do
    should_continue <- parse_input "Enter your command."
    if should_continue then main else return ()
main.hs:9:5: error:                                                                                                                                                                                                                                                            
    • Couldn't match type ‘IO’                                                                                                                                                                                                                                                 
                     with ‘StateT ProgramState Data.Functor.Identity.Identity’                                                                                                                                                                                                 
      Expected type: StateT                                                                                                                                                                                                                                                    
                       ProgramState Data.Functor.Identity.Identity ()                                                                                                                                                                                                          
      Actual type: IO ()

注意:第 9 行是putStr prompt 第 10、12、22、27 行给出了相同的错误。

从那以后我就想到纯粹在 parse_input 内部进行递归,在这种情况下,我似乎不需要 state monad。但我仍然很好奇为什么会出现编译错误。感谢任何帮助,我对 Haskell 很陌生。

【问题讨论】:

    标签: haskell recursion io state monads


    【解决方案1】:

    您似乎将State s a 类型的值与IO a 类型的值混合在一起。在您的main 操作中,您在期望IO 的上下文中调用parse_input。在parse_input 中,您调用putStr 等等,在期望State 的上下文中。那是行不通的!

    做这种事情的通常方法是从State切换到StateT,然后导入Control.Monad.IO.Class。现在,您可以使用

    evalStateT :: StateT s m a -> s -> m a
    

    将循环“降低”到IO,并且

    -- liftIO :: IO a -> StateT s IO a
    liftIO :: MonadIO m => IO a -> m a
    

    在循环中将IO 操作“提升”到StateT。现在(前面有未经测试的代码):

    -- Needed for flexible use of
    -- the MonadState class.
    {-# LANGUAGE FlexibleContexts #-}
    
    import System.IO
    -- You almost always want the "strict"
    -- version of `StateT`; the lazy one is weird.
    import Control.Monad.State.Strict
    import Control.Monad.IO.Class
    
    data ProgramState = ProgramState
        { events :: [Int] }     -- Placeholder for now
    
    -- Renaming your function to follow convention.
    parseInput
      :: (MonadState ProgramState m, MonadIO m)
      => String -> m Bool
    parseInput prompt = do
        str <- liftIO $ do
          putStr prompt
          hFlush stdout
          getLine
        current_state <- get
        case str of
            "c" -> do 
                put (current_state { events = [1,2,3] } )   -- this should become actual appending
                return True
            "l" -> return True    
            "q" -> return False  
            "quit" -> return False
            "h" -> return True
            _ -> do 
                liftIO $ putStrLn "Invalid input."
                parseInput prompt
    
    main :: IO ()
    main = do
        -- You need to supply the initial state; I've just guessed here.
        should_continue <- evalStateT (parseInput "Enter your command.") (ProgramState [])
        if should_continue then main else return ()
    

    正如 Daniel Wagner 指出的那样,这不会将状态从 main 运行到下一个。如果这是你的意图,你可以写

    main :: IO ()
    main = evalStateT loop (ProgramState [])
      where
        loop = do
          should_continue <- parseInput "Enter your command."
          if should_continue then loop else return ()
    

    如果您愿意,可以导入 Control.Monad 并将其缩短为

    main :: IO ()
    main = evalStateT loop (ProgramState [])
      where
        loop = do
          should_continue <- parseInput "Enter your command."
          when should_continue loop
    

    最后说明:如果要捕获循环的最终状态,请使用 runStateT 而不是 evalStateT

    【讨论】:

    • 我认为这不会保留main 递归期间的状态,这似乎是练习的重点。
    • @DanielWagner,它不会。我觉得我对练习的意义没有足够的了解来猜测......
    • 好吧,我给了一个替代方案。
    猜你喜欢
    • 2011-05-08
    • 2019-04-23
    • 2017-11-03
    • 1970-01-01
    • 1970-01-01
    • 2021-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多