map :: (a -> b) -> [a] -> [b]
putStrLn :: Show a => a -> IO ()
map putStrLn :: Show a => [a] -> [IO ()]
你有一个IO ()
动作列表。
main :: IO ()
您需要将它们加入到单个IO ()
操作中。
您要做的是按顺序/ sequence_IO ()
执行这些操作中的每一个:
sequence :: Monad m => [m a] -> m [a]
sequence_ :: Monad m => [m a] -> m ()
为方便起见,mapM / mapM_将在列表上映射一个函数并对生成的一元结果进行排序。
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
所以你的固定代码看起来像这样:
main = mapM_ putStrLn $ map fizzBuzz [1..100]
虽然我可能会这样写:
main = mapM_ (putStrLn . fizzBuzz) [1..100]
甚至这样:
main = putStr $ unlines $ map fizzBuzz [1..100]
让我们编写自己的sequence
. 我们想让它做什么?
sequence [] = return []
sequence (m:ms) = do
x <- m
xs <- sequence ms
return $ x:xs
- 如果列表中没有任何内容,则返回(注入 monad)一个空的结果列表。
- 否则,在单子中,
- 绑定(对于
IO
monad,这意味着执行)第一个结果。
sequence
列表的其余部分;绑定该结果列表。
- 返回第一个结果的 cons 和其他结果的列表。
GHC 的库使用类似的东西,foldr (liftM2 (:)) (return [])
但对新手来说很难解释;现在,相信我的话,它们是等价的。
sequence_
更容易,因为它不需要跟踪结果。GHC 的库将其实现为sequence_ ms = foldr (>>) (return ()) ms
. 让我们扩展 的定义foldr
:
sequence [a, b, c, d]
= foldr (>>) (return ()) [a, b, c, d]
= a >> (b >> (c >> (d >> return ())))
换句话说,“做a
,丢弃结果;做b
;丢弃结果,……最后,返回()
”。
mapM f xs = sequence $ map f xs
mapM_ f xs = sequence_ $ map f xs
另一方面,使用替代unlines
解决方案,您甚至根本不需要了解 monad。
做什么unlines
?嗯,lines "a\nb\nc\nd\n" = ["a", "b", "c", "d"]
当然unlines ["a", "b", "c", "d"] = "a\nb\nc\nd\n"
。
unlines $ map fizzBuzz [1..100]
= unlines ["1", "2", "Fizz", ..]
="1\n2\nFizz\n..."
然后它转到putStr
. 多亏了 Haskell 懒惰的魔力,完整的字符串永远不需要在内存中构造,所以这会很高兴地达到[1..1000000]
或更高:)