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