1

1.

ghci> let x = trace "one" 1 in (x, x)

(one
1,one
1)

I expected let-expr would memorize x, so the result would look like:

(one
1,1)

2.

ghci> let !x = undefined in 1

...
error
...

Ok, strictly evaluated bang-pattern.

ghci> let !x = trace "one" 1 in 1

    1

Because of the strictness I expected the result would look like:

one
1
sof
  • 9,113
  • 16
  • 57
  • 83

1 Answers1

4

You’ve been bitten by the fact that, since GHC 7.8.1, the monomorphism restriction is disabled in GHCi by default. This means that the x binding is generalized to a polymorphic, typeclass-constrained binding with the type

x :: Num a => a

since 1 is a polymorphic number literal. Even though this type does not include any function arrows (->), at runtime, it behaves more like a function than as a value, since it is really a function that accepts a Num typeclass dictionary and uses it to construct its result.

You can avoid this by explicitly annotating the literal to avoid the polymorphism:

ghci> let x = trace "one" (1 :: Integer) in (x, x)
(one
1,1)
ghci> let !x = trace "one" (1 :: Integer) in 1
one
1

Normally, the aforementioned monomorphism restriction is in place, precisely to prevent this kind of confusion where a binding that is syntactically a value definition can have its RHS evaluated multiple times. The linked answer describes some of the tradeoffs of the restriction, but if you want, you can switch it back on, which will make your original examples do what you expect:

ghci> :set -XMonomorphismRestriction
ghci> let x = trace "one" 1 in (x, x)
(one
1,1)
ghci> let !x = trace "one" 1 in 1
one
1
Alexis King
  • 43,109
  • 15
  • 131
  • 205
  • Why doesn't the infinite loop run in `let !x = repeat "" in 1` ? – sof Oct 20 '18 at 17:25
  • @sof The bang-pattern forces the *function* (from a `Num a` dictionary to an `a`), not the value. Indeed, it *can’t* force a value, since no value exists—it doesn’t know what `a` will be. Forcing a function doesn’t cause the function to be invoked, of course, so the bang pattern doesn’t really do anything in that case. – Alexis King Oct 20 '18 at 17:27
  • in this case `let !x = id $ trace "id" "" in 1`, the function `id` is forced to run, why? – sof Oct 20 '18 at 17:39
  • @sof Because in that case, the RHS of the binding (`id $ trace "id" ""`) is monomorphic, so it really does just define a value, not a function (assuming you don’t have `OverloadedStrings` enabled). The critical component to understanding this answer is understanding that a constrained type of the form `C x => a` behaves at runtime like a function `CDict x -> a`, where `CDict` is the type of method dictionaries for the class `C`. Forcing this no more runs the body of the function than forcing a function of type `Integer -> Integer` would. – Alexis King Oct 20 '18 at 18:12