Haskell中foldLeft结合putStrLn的问题及IO操作收集方法
问题解析:
foldLeft在IO操作中的串联问题 简短概述(TL;DR.)
执行以下代码:
foldLeft (\_ x -> putStrLn x) (pure ()) ("2":."1":.Nil)
得到的输出是:
1
而非预期的:
2 1
完整问题背景
我在完成fp-course的练习时,写了两种printFiles的实现:
正确实现
printFiles ps = void . sequence $ (uncurry printFile) <$> ps
该实现的输出符合预期:
>> :main "share/files.txt" ============ share/a.txt the contents of a ============ share/b.txt the contents of b ============ share/c.txt the contents of c
有问题的实现
printFiles = foldLeft (\_ (fp, content) -> printFile fp content) (pure ())
但这个实现只输出最后一个文件的内容:
>> :main "share/files.txt" ============ share/c.txt the contents of c
看起来pure ()(即IO ())只保留了foldLeft中最后一次printFile操作,想知道如何让它收集所有putStrLn操作并按顺序执行。
问题原因与解决方法
核心问题:替换而非串联
你当前的foldLeft累积函数犯了一个关键错误:每次迭代都直接用新的IO操作替换了之前的累积值,而没有把前后的IO操作串联起来。
以TL;DR的例子展开看:
- 第一次迭代:处理元素"2",函数返回
putStrLn "2",直接替换了初始的pure () - 第二次迭代:处理元素"1",函数返回
putStrLn "1",又替换了之前的putStrLn "2"
最终执行的只有最后一个IO操作,所以只输出1。
修正方案:用Monad操作串联IO
IO是Monad,我们可以用>>操作符把前后的IO动作串联起来——先执行累积的所有操作,再执行当前的打印操作。
修正后的printFiles实现:
printFiles = foldLeft (\acc (fp, content) -> acc >> printFile fp content) (pure ())
对应TL;DR的例子,修正后的代码:
foldLeft (\acc x -> acc >> putStrLn x) (pure ()) ("2":."1":.Nil)
执行后会输出:
2 1
原理说明
acc >> printFile ...表示:先执行累积值acc中包含的所有IO操作,再执行当前的printFile操作- 初始值
pure ()是一个空IO动作,不执行任何操作 - 经过
foldLeft迭代后,最终得到的IO动作会按顺序执行所有元素对应的打印操作,和sequence方案的效果一致
内容的提问来源于stack exchange,提问作者zichao liu
相关产品推荐
相关产品推荐

