Reputation: 7719
I'm bit stuck how to rewrite following strict-evaluated list comprehension to use seq instead of bang pattern:
zipWith' f l1 l2 = [ f e1 e2 | (!e1, !e2) <- zip l1 l2 ]
Any idea ?
I've tried
zipWith' f l1 l2 = [ e1 `seq` e2 `seq` f e1 e2 | (e1, e2) <- zip l1 l2 ]
but this unfortunately does not force an evaluation to WHNF.
Upvotes: 2
Views: 778
Reputation: 31
To explain the original poster's example and some of Don's noted behavior. Using (>>=) as a concise concatMap, the original example translates into:
zip l1 l2 >>= \(!e1, !e2) -> [f e1 e2]
The second example is:
zip l1 l2 >>= \(e1, e2) -> [e1 `seq` e2 `seq` f e1 e2]
But what the desugared first version actually further desugars into is:
zip l1 l2 >>= \(e1, e2) -> e1 `seq` e2 `seq` [f e1 e2]
e1 and e2 get forced when the concat in concatMap flattens these singleton lists which provides the "force as I go along" behavior.
While this isn't what one would normally think of as a "strict" zipWith, it is not a fluke that it works for the fibs example.
Upvotes: 3
Reputation: 23014
The essential thing you want the strict zipWith
to do is to evaluate the elements of the list at the time the cons cell is forced. Your function does not do that. Just try
main = print $ length $ zipWith' undefined [1..10] [1..100]
It's a fluke of the strictness analysis when you use (+) that makes it work.
The function you want is something like this:
zipW f (x:xs) (y:ys) = let z = f x y in seq z (z : zipW f xs ys)
zipW _ _ _ = []
You cannot decouple the production of the cons cell from the production of the value, because the former should force the latter.
Upvotes: 5
Reputation: 137947
You can mechanically translate bang patterns into calls to seq
following the GHC
manual:
This:
zipWith' f l1 l2 = [ f e1 e2 | (!e1, !e2) <- zip l1 l2 ]
Becomes Too lazy:
zipWith' f l1 l2 =
[ f e1 e2
| e <- zip l1 l2
, let t = case e of (x,y) -> x `seq` y `seq` (x,y)
, let e1 = fst t
, let e2 = snd t
]
Which is more concisely written as (too lazy):
zipWith' f l1 l2 =
[ f e1 e2
| e <- zip l1 l2
, let (e1,e2) = case e of (x,y) -> x `seq` y `seq` (x,y)
]
Though I'd write it as (wrong, too lazy)
zipWith' f l1 l2 = zipWith (\x y -> uncurry f (k x y)) l1 l2
where
k x y = x `seq` y `seq` (x, y)
You could also move the strictness hint to a data structure:
data P = P !Integer !Integer
zipWith' f l1 l2 = [ f e1 e2 | P e1 e2 <- zipWith P l1 l2 ]
Or as:
zipWith' f l1 l2 = [ f e1 e2 | (e1, e2) <- zipWith k l1 l2 ]
where
k x y = x `seq` y `seq` (x,y)
Upvotes: 6