Pattern match (_, _) demands WHNF of (f st, g st') . Easy.

demands WHNF of . Easy. Pattern match (_, (_,_)) demands WHNF of g st' . Here's the problem, because g is strict, hence it first needs st' in WHNF too. The runtime finds st' in the pattern ((_,st'), (_,_)) , so before it can actually traverse down into st' it needs to WHNF both of the tuples. Because g is strict however, this first requires st' ... and so on.

If you match the result of g with a lazy irrefutable pattern

(<+>) f g st = let ((_,st'), ~(_,st'')) = (f st, g st') in ((),st'')

then the problem goes away, because this is evaluated thus:

Pattern match (_, _) demands WHNF of (f st, g st') . Easy.

demands WHNF of . Easy. Pattern match (_, ~(_,_)) demands nothing more for the time being.

demands nothing more for the time being. Pattern match ((_,st'), ~(_,_)) demands WHNF of f st . Fortunately, we can fulfill this, because st does not depend on the pattern.

demands WHNF of . Fortunately, we can fulfill this, because does not depend on the pattern. Now we've satisfied the pattern match, the runtime can already proceed with the in ((),st'') . Only at this point is the irrefutable pattern ~(_,st'') forced, but this is now not a problem anymore because st' is available here, so it's just a matter of computing g once.

The fixes you've tried all amount to making g non-strict:

remove the deconstruction @(Foo _)

Without that, g doesn't really need to look at its argument to construct the result skeleton, i.e. the tuple match (_,st'') can then succeed without first requiring WHNF of st' .

change data Foo to newtype Foo

This has the effect that the Foo constructor doesn't actually exist at runtime, so there's nothing that the pattern st@(Foo _) would force.

change noOp <+> someOp to someOp <+> noOp