Haskell/理解单子/解决方案/IO
外观
map
函数将打印出列表中的每个值,但会生成 [IO ()]
。因此我们需要链式 sequence $ map
,这正是 mapM
函数的定义方式。因为我们不是收集 print
的结果,而是只关注它的操作,所以我们可以用 mapM_
来丢弃它们。
printList :: Show a => [a] -> IO ()
printList = mapM_ print
如果我们没有使用单子,则将函数 f :: a -> a
应用三次将通过 f(f(f(x)))
实现,这可以改写为 (f . f . f) x
。这表明,如果我们想将其推广到将函数应用 n
次,我们可以使用 (.)
作为累积函数来折叠 f
列表
composeTimes :: Int -> (a -> a) -> (a -> a)
composeTimes n f = foldr (.) id $ replicate n f
foldr
的初始值为 id
,因为 .
现在我们要做的就是将它翻译成单子,即用单子组合运算符 (>=>)
替换 (.)
,用 return
替换 id
,用 generation
替换任意 f
,得到
generations :: Int -> a -> [a]
generations n = foldr (>=>) return $ replicate n generation
我们可以验证它是否按预期工作
Prelude> ["bunny"] >>= generations 0
["bunny"]
Prelude> ["bunny"] >>= generations 1
["bunny","bunny","bunny"]
Prelude> ["bunny"] >>= generations 2
["bunny","bunny","bunny","bunny","bunny","bunny","bunny","bunny","bunny"]
Maybe
单子表示可能的失败。如果在计算列表中的任何时候发生错误,则整个列表将无法计算,因此我们预期列表的 sequence
将在列表中存在 Nothing
时生成 Nothing
,否则生成 Just
列表。这实际上就是发生的情况
Prelude> sequence [Just 1, Just 2, Just 3]
Just [1,2,3]
Prelude> sequence [Just 1, Nothing, Just 3]
Nothing