Skip to content

GHCi inconsistent behaviour with/without `let`

Summary

GHCi is inconsistent in the lasting results of seq (as observed using :sprint) between defining a (monomorphic) value with or without let. To a naive user, this is unexpected: I would have thought let made no difference.

Steps to reproduce

The below works both in GHC 8.10.7 and on current master:

-- the 'map' version
Prelude> a :: [Int] ; a = map (*2) [1..3]
Prelude> :sprint a
a = _
Prelude> seq a ()
()
Prelude> :sprint a
a = _ : _

Prelude> let b :: [Int] ; b = map (*2) [1..3]
Prelude> :sprint b
b = _
Prelude> seq b ()
()
Prelude> :sprint b
b = _ : _

-- the 'id' version
Prelude> c :: [Int] ; c = id [1,2,3]
Prelude> :sprint c
c = _
Prelude> seq c ()
()
Prelude> :sprint c
c = _ : _

Prelude> let d :: [Int] ; d = id [1,2,3]
Prelude> :sprint d
d = _
Prelude> seq d ()
()
Prelude> :sprint d
d = [1,2,3]

-- the 'direct' version
Prelude> e :: [Int] ; e = [1,2,3]
Prelude> :sprint e
e = _
Prelude> seq e ()
()
Prelude> :sprint e
e = _

Prelude> let f :: [Int] ; f = [1,2,3]
Prelude> :sprint f
f = [1,2,3]
Prelude> seq f ()
()
Prelude> :sprint f
f = [1,2,3]

Summarising these results in some tables:

Before seq map id direct
Without let _ _ _
With let _ _ [1,2,3]
After seq map id direct
Without let _ : _ _ : _ _
With let _ : _ [1,2,3] [1,2,3]

(Note: I took care to supply type signatures for everything in order to not fall in the trap where the WHNF of a value of type Num a => f a is a function, not anything related to f a.)

The only strange case in the "before seq" table is the case (before seq, direct, with let), which is explained by #10160; the others entries in that table are unevaluated, which makes sense: nothing has forced evaluation yet.

In the "after seq" table, one would naively expect _:_ everywhere: after all, we have evaluated the list to WHNF using seq, and that is a (:) constructor application. The eager [1,2,3] results in the "with let" row for "id" and "direct" are again attributable to #10160, but what is going on in the case (after seq, direct, without let)? I made a definition and evaluated it to WHNF, but it's still unevaluated! This looks like call-by-name, but it's not always call-by-name, as evidenced by the "map" and "id" cases, where it's call-by-need.

Now of course this behaviour is completely allowed by the Haskell specification, since this is all non-strict. However, I do not expect the with/without let choice to make a difference here!

Is it reasonable to make this more consistent, or would that make the implementation more complex / less maintainable, or make something else less consistent / more confusing?

Expected behavior

I would expect that choosing the non-let declaration style (ghci> x :: [Int] ; x = ...) or the let declaration style (ghci> let x :: [Int] ; x = ...) makes no difference.

Environment

  • GHC version used: 8.10.7, as well as current master, which identifies itself as 9.3.20211115.

Optional:

  • Operating System: Linux
  • System Architecture: x86_64
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information