你没看错,类型是y :: [IO String]。
嗯,这里基本上有两个主要部分:
如何将[IO String]变成IO [String]
[IO String] 是IO 操作的列表,我们需要的是一个携带字符串列表(即IO [String])的IO 操作。幸运的是,函数序列正好提供了我们所需要的:
sequence :: Monad m => [m a] -> m [a]
y' = sequence y :: IO [String]
现在mapM函数可以简化这一点,我们可以将y'改写为:
y' = mapM readFile directoryContents
mapM 为我们完成序列。
如何获得 [String]
我们的类型现在是IO [String],所以现在的问题是“我们如何从 IO 中取出 [String]?”这就是函数>>= (bind) 的作用:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
-- Specialized to IO, that type is:
(>>=) :: IO a -> (a -> IO b) -> IO b
我们还有一个函数return :: Monad m => a -> m a,它可以将值“放入”IO。
所以有了这两个函数,如果我们有一些函数f :: [String] -> SomeType,我们可以这样写:
ourResult = y' >>= (\theStringList -> return (f theStringList)) :: IO SomeType
函数可以与>>= 函数“链接”在一起。有时这可能有点难以理解,因此 Haskell 提供了do 符号以使事情在视觉上更简单:
ourResult = do
theStringList <- y'
return $ f theStringList
编译器在内部将其转换为y' >>= (\theStringList -> f theStringList),与我们之前的y' >>= f 相同。
把它们放在一起
我们实际上可能不希望 y' 到处乱跑,所以我们可以消除它并得出:
ourResult = do
theStringList <- mapM readFile directoryContents
return $ f theStringList
更加简化
事实证明,这实际上并不需要>>= 的全部功能。事实上,我们只需要fmap!这是因为函数 f 只有一个“内部”参数 IO 并且我们没有使用任何其他以前的 IO 结果:我们正在生成结果然后立即使用它。
使用法律
fmap f xs == xs >>= return . f
我们可以像这样重写>>= 代码来使用fmap:
ourResult = fmap f (mapM readFile directoryContents)
如果我们想要更简洁,fmap 的中缀同义词称为<$>:
ourResult = f <$> mapM readFile directoryContents