无法在foldr中执行I / O?

时间:2011-05-16 20:49:09

标签: haskell io monads fold

我有一个Data.Map结构,可将String映射到Strings。无论出于何种原因,我想使用key: valuefoldrWithKey格式打印地图内容,如下所示:

M.foldrWithKey (\k v b -> putStrLn (k++": "++v++"\n")) (return ()) data

但是,只有地图的第一个元素出现在输出中(即使地图有多个元素)。但是,当我尝试使用foldrWithKey构建列表然后打印它时,所有元素都显示出来:

print (M.foldrWithKey (\k v b -> k:b) [] data)

那么,为什么其他元素在尝试执行I / O时不会出现?它是折叠器的工作方式还是有一些我遗漏的微妙的懒惰相关的怪癖?

4 个答案:

答案 0 :(得分:23)

这是因为正确的折叠是如何工作的,是的。折叠函数是一个累加:在每一步,给定一个元素(在这种情况下,键和值)和其余数据的累积结果,它将它们组合成一个结果。整个折叠以递归方式汇总整个数据集。

在您的情况下,您将丢弃累加器函数的“结果”输入 - 请注意,永远不会使用b参数。请记住,IO a不仅仅是a类型的值,附加了一些额外的垃圾,它实际表示的是计算产生a,并且该计算只会是通过将其与其他计算相结合来运行,作为main函数的最终值的一部分(或者,在GHCi中,被评估的表达式)。

通过丢弃累计值,其他计算永远不会成为最终结果的一部分,因此值永远不会被打印。

从你提出问题的方式来看,我猜你在命令式编程方面比Haskell的功能风格更舒服。显然,在一种命令式语言中,在折叠过程中打印实际上是“发生”的某种有意义的意义,假设累积值是无用的是合理的。如果它有所帮助,可以将其视为一种元编程;你实际上并没有编写一个循环来打印值,你正在构建一个执行实际打印的命令式程序,并且通过丢弃累积的值,你基本上丢弃了除了展开的循环的第一行以外的所有内容,滥用比喻很糟糕。

无论如何,在这种情况下您可能想要的是“打印剩余的数据”操作,b参数,并使用{{putStrLn ...操作将其合并1}},一个基本上意味着“执行第一个动作,忽略结果,执行第二个动作”的运算符。这是命令式“循环中的print语句”的直接翻译。


另外,虽然我完全理解这一点,但我总是可以避免混合格式和打印方式。在我看来,将每个键/值对分别格式化为列表似乎更整洁,然后仅(>>)

mapM_ putStrLn是一个高阶函数,描述了你在这里所做的事情的本质;给定一个类型mapM_的列表和一个将a转换为某种a动作的函数,它将函数应用于每个项目并按顺序运行生成的动作列表。 IO类型mapM_起初看起来很神秘,但Haskell的一个好处是,一旦习惯了阅读类型签名,Monad m => (a -> m b) -> [a] -> m ()的类型不仅可以理解一目了然,它几乎是自我记录的,因为对于具有该类型的函数而言,只有一个明智的事情,这正是mapM_本身所做的事情。

答案 1 :(得分:11)

以下是一个更清晰的示例,说明正在发生的事情,而不使用I / O.

foldr (\x b -> x) 9 [8,7,6,5,4,3,2,1,0]

此表达式返回列表的头部8。列表的其余部分在哪里?好吧,处理列表其余部分的结果是在'b'中传递,没有使用,所以列表的其余部分就被忽略了。

在你的情况下也会发生同样的事情。通过忽略累加器'b',您构建的I / O操作仅使用映射的一个元素。基本上你已经说过,“打印地图,打印它的第一把钥匙和价值。”您应该说的是,“打印地图,打印第一个键和值,然后打印地图的其余部分。”为此,您需要安排在调用putStrLn后运行变量'b'的内容:

M.foldrWithKey (\k v b -> do {putStrLn (k ++ ": " ++ v ++ "\n"); b}) (return ()) d

答案 2 :(得分:6)

混合使用IO和漂亮的打印方式有点糟糕,所以如何将IO浮出来:

> putStr $ foldrWithKey (\k v b -> b ++ k ++ ": "++v++"\n") [] m

现在,至于您的代码无法正常工作的原因,请考虑您的代码正在构建的内容:b参数中的一系列print语句。但是,每次循环都会丢弃b

所以要跟踪它:

> foldrWithKey (\k v b -> putStrLn (k++": "++v) >> b) (return ()) m   

教训,不要扔掉你的蓄电池。

答案 3 :(得分:3)

当你用(\ kvb - > putStrLn(k ++“:”++ v ++“\ n”))折叠时你不在任何地方使用b,所以剩下的就是最后一个IO()离开了。因此它打印第一个值。您可以通过折叠(\ k v b - > putStrLn(k ++“:”++ v ++“\ n”)>> b)来阻止这种情况。