Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Time cost of Haskell `seq` operator

This FAQ says that

The seq operator is

seq :: a -> b -> b

x seq y will evaluate x, enough to check that it is not bottom, then discard the result and evaluate y. This might not seem useful, but it means that x is guaranteed to be evaluated before y is considered.

That's awfully nice of Haskell, but does it mean that in

x `seq` f x

the cost of evaluating x will be paid twice ("discard the result")?

like image 963
quant_dev Avatar asked Mar 14 '12 17:03

quant_dev


4 Answers

The seq function will discard the value of x, but since the value has been evaluated, all references to x are "updated" to no longer point to the unevaluated version of x, but to instead point to the evaluated version. So, even though seq evaluates and discards x, the value has been evaluated for other users of x as well, leading to no repeated evaluations.

like image 106
dflemstr Avatar answered Nov 16 '22 08:11

dflemstr


No, it's not compute and forget, it's compute - which forces caching.

For example, consider this code:

 let x = 1 + 1
 in x + 1

Since Haskell is lazy, this evaluates to ((1 + 1) + 1). A thunk, containing the sum of a thunk and one, the inner thunk being one plus one.

Let's use javascript, a non-lazy language, to show what this looks like:

 function(){
   var x = function(){ return 1 + 1 };
   return x() + 1;
 }

Chaining together thunks like this can cause stack overflows, if done repeatedly, so seq to the rescue.

let x = 1 + 1
in x `seq` (x + 1)

I'm lying when I tell you this evaluates to (2 + 1), but that's almost true - it's just that the calculation of the 2 is forced to happen before the rest happens (but the 2 is still calculated lazily).

Going back to javascript:

 function(){
   var x = function(){ return 1 + 1 };
   return (function(x){
     return x + 1;
   })( x() );
 }
like image 24
rampion Avatar answered Nov 16 '22 07:11

rampion


I believe x will only be evaluated once (and the result retained for future use, as is typical for lazy operations). That behavior is what makes seq useful.

like image 4
comingstorm Avatar answered Nov 16 '22 06:11

comingstorm


You can always check with unsafePerformIO or trace

import System.IO.Unsafe (unsafePerformIO)

main = print (x `seq` f (x + x))
  where
    f = (+4)
    x = unsafePerformIO $ print "Batman!" >> return 3
like image 1
Mischa Arefiev Avatar answered Nov 16 '22 06:11

Mischa Arefiev