我有点理解State Monad在顺序执行中传播新值的有用性。但是在以下代码中,我在理解addResult
每次获得评估时如何以及在何处获得更新状态时遇到了麻烦。
fizzBuzz :: Integer -> String
fizzBuzz n
| n `mod` 15 == 0 = "FizzBuzz"
| n `mod` 5 == 0 = "Buzz"
| n `mod` 3 == 0 = "Fizz"
| otherwise = show n
fizzbuzzList :: [Integer] -> [String]
fizzbuzzList list = execState (mapM_ addResult list) []
addResult :: Integer -> State [String] ()
addResult n = do
xs <- get
let result = fizzBuzz n
put (result : xs)
main :: IO ()
main = mapM_ putStrLn $ fizzbuzzList [1..100]
此代码评估为产生
1、2,嘶嘶声...
我只是想不通addResult
产生的新值如何附加到先前产生的列表中。您能帮我了解一下mapM_ addResult list
是怎么回事吗?
答案 0 :(得分:3)
正如您已经正确观察到的那样,State monad用于通过一系列计算来对某些外部“状态”值进行线程化。但是,您问如何在列表addResult :: Integer -> State [String] ()
的每个值上对函数list
的多次调用中“保持”这种状态。诀窍是mapM_
的定义。我们将从考虑更简单的功能mapM
开始:
mapM f [] = return []
mapM f (x:xs) = do
fx <- f x
fxs <- mapM f xs
return (fx : fxs)
如果我们用示例列表[x1,x2,x3,x4,...,xn]
来“扩展”此递归定义,我们将观察到mapM f [x1,...,xn]
的值将是另一种单子计算:
do
fx1 <- f x1
fx2 <- f x2
-- etc.
fxn <- f xn
return [fx1,fx2,...,fxn]
因此,mapM
基本上是通过按顺序运行将一堆单子计算“组合”为一个大的。这就解释了如何构建列表而不是生成许多较小的列表:get
开头的addResult
从上次运行(而不是从开始)获取状态,因为您正在同时运行所有计算,所以:
do
fl0 <- addResult (list !! 0)
fl1 <- addResult (list !! 1)
-- etc. like before
(如果仔细阅读,您会注意到我一直在谈论mapM
,但是您实际上使用过mapM_
。它们完全一样,只是后者返回{{1 }}。
答案 1 :(得分:2)
State
定义为
data State s a = s -> (a, s)
这意味着
Integer -> State [String] ()
等同于
Integer -> [String] -> ((), [String])
因此addResult
接受一个整数并返回一个 function ,该函数接受一个状态并返回一个包含新状态的元组。
mapM_
将一组这些功能链接在一起。使用常规映射将产生State
个函数的列表,每个函数都期望一个状态并返回一个新状态。 mapM_
采取了进一步的步骤,将每个State
绑定到前一个。最终结果不是State
值的列表,而是构成管道的单个State
值。
execState
然后在管道的一端提供初始状态,并从另一端返回最终状态。