Haskell: 如何处理嵌套的IO monad中的IO monad?

4

我刚开始学习Haskell,一周后就爱上它了。目前正在经历单子(monads)之痛,虽然还没有完全掌握,但希望有一天能理解。

我正在尝试组合一个类似于Python walk()函数的功能,但要更简单些。给定一个路径,我想生成一个元组列表,每个子目录对应一个元组(假设只有目录)。元组的第一个元素是目录的路径,第二个元素是目录包含的文件列表。

我不知道我是否解释清楚了,但这里是代码:

walkDir :: String -> IO [IO (FilePath, [FilePath])]
walkDir path = do
  dir <- getDirectoryContents path  
  let nd = [x | x <- dir, notElem x [".",".."]]
  return (map getcont nd)
    where
      getcont path = do
      cont <- getDirectoryContents path
      return (path,cont)

我关注的是IO内部的问题以及如何处理它?是否有可能取消它们?是否有可能至少取消掉内部IO?这种返回方式正常吗?

我甚至不能打印出这种返回结果。我需要为此创建一个show实例才能正确打印吗?

某些Haskell库中可能存在类似的函数,但这是为了教育目的而编写的。我想学习。因此,欢迎任何建议。

非常感谢。


7
关于“我关心的是嵌套IO如何处理?是否可以将其取消?”这个问题——这正是单子(monads)所涉及的!一个基本的单子函数(非常基础,你可以通过定义单子和 'return' 来定义它)是 join :: Monad m => m (m a) -> m a。从签名中可以看出,它的整个目的就是“取消”那个额外的单子层级。就像梦境一样,嵌套在单子中的单子也只是单子而已 :) - Tom Crockett
不要忘记,在Haskell中,return的含义与大多数编程语言中的含义非常不同。 - Dan Burton
2个回答

10

请查看Control.Monad,以获取mapM函数。

接下来是:

return (map getcont nd)

变成

mapM getcont nd

6
还有一个函数join :: (Monad m) => m (m a) -> m a,可以在你遇到类似情况时帮助你。 - ivanm
谢谢大家 :) 这正是我在寻找的。我还没有深入研究单子的东西。试图先学会基础... - r.sendecky
@r.sendecky:将这个答案标记为解决方案,以关闭此问题。 - ivanm

4
让我们来看看类型。
map :: (a -> b) -> [a] -> [b]
getCont :: FilePath -> IO (FilePath, [FilePath])
nd :: [FilePath]

map getCont nd :: [IO (FilePath, FilePath)]

现在,到这一步,结构看起来很内向。我们有[IO a]但我们想要IO [a]。停!Hoogle 时间到了。为了概括任何旧的单子,我们搜索[m a] -> m [a]。哎呀,sequence正好具有这种类型签名。因此,您应该使用sequence :: [m a] -> m [a],而不是return :: a -> m a,如下所示:

sequence (map getCont nd)

那么您就可以准备好了。请注意,这与Kurt S的解决方案基本相同,因为
mapM f xs = sequence (map f xs)

谢谢。我从这里了解到了Hoogle。当Kurt在他的帖子中提到mapM但没有足够地运用它时,我间接地了解了序列。 - r.sendecky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接