我有一个Data.Map
结构,可将String
映射到Strings
。无论出于何种原因,我想使用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时不会出现?它是折叠器的工作方式还是有一些我遗漏的微妙的懒惰相关的怪癖?
答案 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)来阻止这种情况。