Проблема в том, что foldl'
только уменьшает аккумулятор до WHNF на каждом шаге.В этом случае аккумулятором является действие IO
, а оценка действия IO
не приводит к принудительному изменению значения, поэтому вы получите типичный огромный поток, который не будет оценен до конца.
Решение состоит в том, чтобы использовать что-то более строгое, чем 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
Как видите, это все еще медленнее, чем чистый код, но не так сильно.