17

我有一个将 sData.Map映射StringStringss 的结构。无论出于何种原因,我想key: value使用格式打印地图的内容foldrWithKey,如下所示:

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

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

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

那么,为什么在尝试执行 I/O 时其他元素没有出现?是 foldr 的工作方式还是我遗漏了一些与惰性 io 相关的微妙怪癖?

4

4 回答 4

22

这是因为正确的折叠是如何工作的,是的。折叠函数是一种累加:在每一步,给定一个元素(在本例中为键和值)和其余数据的累加结果,它将它们组合成一个结果。折叠作为一个整体递归地总结整个数据集。

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

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

从您提出问题的方式来看,我猜您在命令式编程中仍然比 Haskell 的函数式编程更舒服。显然,在某种有意义的意义上,在折叠过程中打印实际上“发生”的命令式语言中,假设累积值是无用的是合理的。如果有帮助,请将此更多地视为一种元编程;您实际上并没有编写循环来打印值,而是构建了一个执行实际打印的命令式程序,并且通过丢弃累积的值,您基本上丢弃了展开循环的第一行以外的所有内容,以滥用比喻不好。

无论如何,在这种情况下,您可能想要的是采取“打印其余数据”操作,b参数,并将其与putStrLn ...操作结合使用(>>),该操作符基本上意味着“执行第一个操作,忽略结果,执行第二个”。这是对命令式“循环打印语句”的非常直接的翻译。


此外,虽然我理解这完全是题外话,但我可能会避免以这种方式混合格式和打印。在我看来,将每个键/值对分别格式化为一个列表似乎更整洁,然后再过mapM_ putStrLn一遍。

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

于 2011-05-16T21:07:03.460 回答
11

这是一个不使用 I/O 的情况下正在发生的更清晰的示例。

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

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

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

M.foldrWithKey (\k v b -> do {putStrLn (k ++ ": " ++ v ++ "\n"); b}) (return ()) d
于 2011-05-16T21:08:09.783 回答
6

像这样混合 IO 和漂亮的打印有点不好,所以如何将 IO 浮动出来:

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

现在,至于为什么您的代码不起作用,请考虑一下您的折叠正在构建什么:b参数中的一系列打印语句。b但是,您每次都在循环中扔掉!

所以跟踪它:

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

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

于 2011-05-16T21:10:19.340 回答
3

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

于 2011-05-16T21:07:37.723 回答