在交互式命令行提示期间保持状态
Maintaining state during interactive command line prompt
我想写一个玩具程序,它有一个交互式提示,可以保存和显示所有以前的输入。这是我的第一次尝试,但没有编译(使用 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 内部进行递归,在这种情况下我似乎不需要状态 monad。但是我仍然很好奇为什么会出现编译错误。感谢任何帮助,我是 Haskell.
的新手
您似乎将 State s a
类型的值与 IO a
类型的值混合在一起。在您的 main
操作中,您在期望 IO
的上下文中调用 parse_input
。在 parse_input
中,您在需要 State
的上下文中调用 putStr
等等。那是行不通的!
通常做这种事情的方法是从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
。
我想写一个玩具程序,它有一个交互式提示,可以保存和显示所有以前的输入。这是我的第一次尝试,但没有编译(使用 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 内部进行递归,在这种情况下我似乎不需要状态 monad。但是我仍然很好奇为什么会出现编译错误。感谢任何帮助,我是 Haskell.
的新手您似乎将 State s a
类型的值与 IO a
类型的值混合在一起。在您的 main
操作中,您在期望 IO
的上下文中调用 parse_input
。在 parse_input
中,您在需要 State
的上下文中调用 putStr
等等。那是行不通的!
通常做这种事情的方法是从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
。