map :: (a -> b) -> [a] -> [b]
putStrLn :: Show a => a -> IO ()
map putStrLn :: Show a => [a] -> [IO ()]
您有一个 IO ()
操作列表。
main :: IO ()
您需要将它们合并为单个IO ()
操作。
您想要做的是在sequence/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 [] = return []
sequence (m:ms) = do
x <- m
xs <- sequence ms
return $ x:xs
- 如果列表里面没有元素了,就返回(注入到Monad中)一个空结果列表。
- 否则,在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
解决方案甚至无需了解单子(monads)。
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]
或更高也能快乐运行 :)
IO()
操作列表,但您需要说明您想要对它们执行什么操作。sequence
按顺序运行一系列单子,这恰好是您在此处想要的。相反,我的最后一个解决方案将 fizz/buzz 字符串连接成一个多行字符串,然后进入IO
的奇怪领域。 - ephemient