如何折叠懒惰类型列表([IO a])?
我不知道这是否是一个有效的术语'懒惰类型'。 但是,IO仍然很懒惰
import Control.Monad
import Data.List
result :: IO Double
result = foldl1' (liftM2 (+)) $ map return [1..10000000]
result' :: IO Double
result' = return $ foldl1' (+) [1..10000000]
result
很慢并且使用大量内存,而不像result'
。 我该如何折叠[IO a]
?
result
构造一个大的IO Double
值,而不会评估任何中间结果,只有当总结果需要时才会发生,例如用于打印。 foldl'
将中间结果评估为弱头标准形式,也就是最外层的构造函数或lambda。 由于(在GHC中), IO a
具有构造函数IO
,因此折叠的中间结果具有这种形式
IO (some computation combined with another)
IO
下的表达式在每一步都变得更加复杂。
为了避免这种情况,您不得不强制中间IO
值,而且还要强制返回它们的值,
main :: IO ()
main = foldlM' (a -> fmap (a+)) 0 (map return [1.0 .. 10000000]) >>= print
foldlM' :: Monad m => (a -> b -> m a) -> a -> [b] -> m a
foldlM' foo a [] = return a
foldlM' foo a (b:bs) = do
c <- foo a b
c `seq` foldlM' foo c bs
适用于您的示例。
问题在于foldl'
只会在每一步中将累加器减少到WHNF。 在这种情况下,累加器是一个IO
动作,并且评估一个IO
动作并不会强制内部的值,所以最终会得到一个典型的巨大thunk,直到最后才会被评估。
解决方案是使用比liftM2
更严格的东西,例如:
result'' :: IO Double
result'' = foldl1' f $ map return [1..100000]
where f mx my = do x <- mx; y <- my; return $! x + y
这是一个快速的基准:
import Control.Monad
import Data.List
import Criterion.Main
result :: IO Double
result = foldl1' (liftM2 (+)) $ map return [1..100000]
result' :: IO Double
result' = return $ foldl1' (+) [1..100000]
result'' :: IO Double
result'' = foldl1' f $ map return [1..100000]
where f mx my = do x <- mx; y <- my; return $! x + y
main = defaultMain [ bench "result" $ whnfIO result
, bench "result'" $ whnfIO result'
, bench "result''" $ whnfIO result'' ]
结果:
[...]
benchmarking result
collecting 100 samples, 1 iterations each, in estimated 37.32438 s
mean: 136.3221 ms, lb 131.4504 ms, ub 140.8238 ms, ci 0.950
std dev: 23.92297 ms, lb 22.00429 ms, ub 25.53803 ms, ci 0.950
benchmarking result'
collecting 100 samples, 14 iterations each, in estimated 6.046951 s
mean: 4.349027 ms, lb 4.338121 ms, ub 4.367363 ms, ci 0.950
std dev: 70.96316 us, lb 49.01322 us, ub 113.0399 us, ci 0.950
benchmarking result''
collecting 100 samples, 2 iterations each, in estimated 8.131099 s
mean: 41.89589 ms, lb 40.67513 ms, ub 43.52798 ms, ci 0.950
std dev: 7.194770 ms, lb 5.758892 ms, ub 8.529327 ms, ci 0.950
正如你所看到的,这仍然比纯代码慢,但不是那么多。
根据Daniel的回答,这个变体允许直接实现foldl1'
的模拟:
result'' :: IO Double
result'' = foldlM1' (+) (map return [1.0 .. 100000000])
foldlM' :: Monad m => (a -> b -> a) -> m a -> [m b] -> m a
foldlM' foo ma [] = ma
foldlM' foo ma (mb:mbs) = do
c <- (liftM2 foo) ma mb
c `seq` foldlM' foo (return c) mbs
foldlM1' foo (x:xs) = foldlM' foo x xs
foldlM1' _ [] = error "Empty list for foldlM1'"
链接地址: http://www.djcxy.com/p/42987.html