我有一个将Strings映射到Stringss的Data.Map结构。无论出于什么原因,我都希望使用foldrWithKey以key: value格式打印地图内容,如下所示:
M.foldrWithKey (\k v b -> putStrLn (k++": "++v++"\n")) (return ()) data但是,只有map的第一个元素出现在输出中(即使map有多个元素)。但是,当我尝试使用foldrWithKey构造一个列表并打印它时,所有的元素都显示出来了:
print (M.foldrWithKey (\k v b -> k:b) [] data)那么,为什么其他元素在尝试执行I/O时没有出现呢?是foldr的工作方式,还是我遗漏了一些与lazy-io相关的微妙怪癖?
发布于 2011-05-17 05:07:03
这是因为右折叠的工作方式,是的。折叠函数是一个累加:在每个步骤中,给定一个元素(在本例中是键和值)和其余数据的累加结果,它将它们组合成一个结果。作为一个整体,文件夹递归地执行该操作,以汇总整个数据集。
在本例中,您将丢弃累加器函数的"result“输入--请注意,从未使用过b参数。请记住,IO a不仅仅是附加了一些额外垃圾的a类型的值,它实际表示的是生成a的计算,并且该计算只能通过将其与其他计算组合在一起作为main函数(或者,在GHCi中,是被计算的表达式)的最终值的一部分来运行。
通过丢弃累加值,其他计算永远不会成为最终结果的一部分,因此这些值永远不会打印出来。
从您提出这个问题的方式来看,我猜您仍然更喜欢命令式编程,而不是Haskell的函数式编程。显然,在一种命令式语言中,在某种意义上,打印实际上是在折叠过程中“发生”的,因此可以合理地假设累加值是无用的。如果有帮助,可以更多地将其视为一种元编程;您实际上不是在编写一个用于打印值的循环,而是在构造一个执行实际打印的命令式程序,通过丢弃累加值,您基本上丢弃了展开的循环中除第一行以外的所有行,从而严重滥用了类比。
无论如何,在这种情况下,您可能需要采取“打印数据的其余部分”操作,即b参数,并使用(>>)将其与putStrLn ...操作组合在一起,这是一个运算符,基本上意味着“执行第一个操作,忽略结果,执行第二个操作”。这是对命令式风格“循环中的打印语句”的非常直接的翻译。
此外,虽然我知道这完全不是重点,但我可能会避免以任何方式混合格式和打印。在我看来,将每个键/值对分别格式化为一个列表,然后对其执行mapM_ putStrLn操作似乎更整洁。
mapM_是一个高阶函数,它描述了您在这里所做的事情的本质;给定一个a类型的列表和一个将a转换为某种IO操作的函数,它会将该函数应用于每一项,并按顺序运行生成的操作列表。Haskell的类型Monad m => (a -> m b) -> [a] -> m ()乍一看似乎很神秘,但是关于mapM_的好处之一是,一旦你习惯于阅读类型签名,Haskell的类型不仅一目了然,而且几乎是自记录的,因为对于具有该类型的函数来说,只有一件明智的事情要做,那就是mapM_本身所做的事情。
发布于 2011-05-17 05:08:10
这是一个不使用I/O的更清晰的示例。
foldr (\x b -> x) 9 [8,7,6,5,4,3,2,1,0]此表达式返回列表的头部8。列表的其余部分都去了哪里?那么,处理列表的其余部分的结果被传递到'b‘中,它没有被使用,所以列表的其余部分被简单地忽略。
同样的事情也发生在你的案例中。通过忽略累加器'b',您正在构造一个只使用map的一个元素的I/O操作。基本上,您已经说过,“要打印地图,请打印它的第一个键和值。”您应该说的是,“要打印一个地图,先打印它的第一个键和值,然后再打印地图的其余部分。”为此,您需要安排变量'b‘的内容在putStrLn调用运行后运行:
M.foldrWithKey (\k v b -> do {putStrLn (k ++ ": " ++ v ++ "\n"); b}) (return ()) d发布于 2011-05-17 05:10:19
将IO和漂亮的打印混合在一起是有点糟糕的形式,所以将IO浮动出来如何:
> putStr $ foldrWithKey (\k v b -> b ++ k ++ ": "++v++"\n") [] m现在,至于代码不能正常工作的原因,考虑一下您的fold正在构建什么:b参数中的一系列打印语句。但是,每次循环都会丢弃b!
因此,请跟踪它:
> foldrWithKey (\k v b -> putStrLn (k++": "++v) >> b) (return ()) m 教训,不要扔掉你的累加器。
https://stackoverflow.com/questions/6023237
复制相似问题