在IO操作中传递状态

时间:2014-05-28 15:33:53

标签: haskell state

我逐行解析文件,并希望根据每行包含的内容执行某些操作。其中一些操作会更改对象的状态,而某些操作则需要打印当前状态。

--code
data Adder = Adder { 
  cur :: Int, 
} deriving (Show)

initAdder :: Adder
initAdder = Adder 0

main = do
  [fname] <- getArgs
  input   <- readFile fname
  mapM_ process (lines input) 

-- file to read-------------------------
+1
-4
print
*45
+6
-5
print
-----------------------------------------

问题是我应该如何编写函数进程才能传递Adder类型的相同对象,有时执行IO操作(打印)或只是改变状态并继续?感谢。

2 个答案:

答案 0 :(得分:2)

要理解的一点是IO并不意味着国家。为此,State包中有mtl monad。如果您需要同时管理状态和IO,则表示您希望将两种不同类型的效果组合在一起,这会提示StateT monad变换器。你可以使用类似

的东西
import Text.Read (readMaybe)
import Control.Monad.State

-- The same state data type
data Adder = Adder { cur :: Int } deriving (Eq, Show)

-- Some helper functions for doing math on Adders
add :: Int -> Adder -> Adder
add x (Adder cur) = Adder (cur + x)

-- Note that sub x y === y - x
sub :: Int -> Adder -> Adder
sub x (Adder cur) = Adder (cur - x)

mul :: Int -> Adder -> Adder
mul x (Adder cur) = Adder (cur * x)

-- Just an alias to reduce typing and errors
type App a = StateT Adder IO a

process :: String -> App ()
-- If the line starts with an arithmetic operator, use the appropriate function to modify the current state
-- Here, I'm using readMaybe to safely convert the value to an Int, doing nothing if the value can't be parsed
process ('+':val) = maybe (return ()) (\x -> modify (add x)) $ readMaybe val
process ('-':val) = maybe (return ()) (\x -> modify (sub x)) $ readMaybe val
process ('*':val) = maybe (return ()) (\x -> modify (mul x)) $ readMaybe val
-- If the line is "print", then just print the current state
process "print"   = do
    -- Get the current state
    Adder cur <- get
    -- Since we're using the StateT transformer, have to use liftIO to perform IO actions
    liftIO $ print cur

-- A simple app that executes a sequence of statements with state
runApp :: [String] -> IO Adder
runApp fileLines = execStateT (mapM_ process fileLines) (Adder 0)

main :: IO ()
main = do
    (fname:_) <- getArgs
    input     <- readFile fname
    runApp $ lines input

当然,如果您仅使用Int作为州而不是Adder,则此代码可能会短得多。您不需要辅助函数,process可以写为

process :: String -> App ()
process ('+':val) = maybe (return ()) (\x -> modify (+x)) $ readMaybe val
process ('-':val) = maybe (return ()) (\x -> modify (subtract x)) $ readMaybe val
process ('*':val) = maybe (return ()) (\x -> modify (*x)) $ readMaybe val
process "print"   = get >>= liftIO . print  -- Shortened this to a one-liner too

答案 1 :(得分:0)

我在不使用State monad和变形金刚的情况下写了一个简化到这个程序的地面版本,因为我还不熟悉它们。也许它会对像我这样的Haskell新手有用。

main = do
  [fname] <- getArgs
  input   <- readFile fname
  process 0 $ lines input

process :: Int -> [String] -> IO()
process _ []      = return ()
process n (x:xs)
  | head x == '+' || head x == '-' = do
    process n' xs
  | head x == 'p' = do
    print n
    process n xs
  | otherwise     = return ()
    where
      n' 
        | head x == '+' = n + (read (tail x) :: Int)
        | otherwise     = n - (read (tail x) :: Int)