I've been trying to understand how shared computation works in Haskell. According to my understanding, point-free shared computation should be evaluated only once (courtesy to CSE).
(A) For example, consider the following code and its output:
*Main> let foo = trace "eval foo" 5 in foo + foo
eval foo
10
*Main> let foo' = \x -> trace "eval foo'" x in (foo' 5) + (foo' 5)
eval foo'
eval foo'
10
As expected, foo
is evaluated only once (CSE probably kicks in), while foo'
is evaluated lazily twice. That is fine. I tried the above using GHCi, version 7.6.3. Next, I tried the same code in GHCi, version 8.6.5, yielding this result instead:
*Main> let foo = trace "eval foo" 5 in foo + foo
eval foo
eval foo
10
Notice that foo
is evaluated twice now.
(B) Similarly, with GHCi, version 7.6.3:
*Main> let goo = const (trace "eval goo" 5) in goo () + goo ()
eval goo
10
but, GHCi, version 8.6.5 evaluates goo
twice:
*Main> let goo = const (trace "eval goo" 5) in goo () + goo ()
eval goo
eval goo
10
(C) Finally, both versions yield the same result for the below:
*Main> let foo_wrapper x = let foo = trace "eval foo" x in foo + foo
*Main> foo_wrapper 5
eval foo
10
I wonder if some optimizations have been turned off by default in GHCi-8 or if the side effects of trace
force foo
to be evaluated somehow twice? Or was there an issue in GHCi-7? How is GHCi supposed to behave with expressions like (A) and (B)?
(Update 1)
For scenario (C) consider the following runs in GHCi-8 (with the main difference in the second argument of trace
):
*Main> let foo_wrapper x = let foo = trace "eval foo" x in foo + foo
*Main> foo_wrapper 5
eval foo
10
*Main> :t (foo_wrapper 5)
(foo_wrapper 5) :: Num a => a
*Main> let foo_wrapper' x = let foo = trace "eval foo" 5 in foo + foo
*Main> foo_wrapper' ()
eval foo
eval foo
10
*Main> :t (foo_wrapper' ())
(foo_wrapper' ()) :: Num a => a