9

In GHCi: (> means output)

data Unit = Unit
let x = Unit
let y = ()
:p x
> x = (_t1::Unit)
:p y
> y = ()
:i ()
> data () = ()    -- Defined in `GHC.Tuple'

Why do Unit and () behave differently? There are also other types that behave like (), for example Int and Char. Are there any other such types?

Surprisingly, when I replace () by undefined, it behaves as I expected again:

let y = undefined :: ()
:p y
y = (_t2::())
bennofs
  • 11,873
  • 1
  • 38
  • 62
  • I don't fully understand this difference either, but it seems to have to do with the code being compiled or not. For example `let x = True` and `:p x` also shows `x = True`. And compiling the module with the `Unit` definition makes it match the behaviour of `()`. – kosmikus Feb 12 '14 at 17:15
  • I have an idea. The `Unit` constructor is like a top level constant. If it is evaluated once, it stays evaluated for the whole run time of the program. I guess some values are `evaluated` (because almost every program needs them) by default. You can test this when you try `let x = Unit; y = Unit`. Even if you only evaluate `y`, x will be evaluated too after. – bennofs Feb 12 '14 at 17:19
  • @bennofs If you make the type `newtype Unit = Unit ()`, or `data Unit = Unit ()`, you also get different behavior, they behave more like `()`. – bheklilr Feb 12 '14 at 17:35

1 Answers1

8

You may want to look at this GHCi session:

Prelude> data Unit = Unit deriving Show
Prelude> let x = Unit
Prelude> let x2 = Unit
Prelude> :p x
x = (_t1::Unit)
Prelude> x2
Unit
Prelude> :p x
x = <.:Interactive.Unit>
Prelude> let x3 = Unit
Prelude> :p x3
x3 = <.:Interactive.Unit>

Basically, after x and x2 are defined, x is an unevaluated thunk. After I force the evaluation of x2, we discover that x was evaluated as well. Variable x3, despite being defined later, is never in an unevaluated state.

I believe that GHCi is playing some optimization here, creating just a single thunk for Unit. Every time you use the value constructor Unit, GHCi re-uses that thunk. Hence, the first time the single thunk is forced, every variable defined through the constructor appears in an evaluated state.

IIRC, small Integer literals use a shared pre-evaluated thunk as well:

Prelude> let y3 = 3 :: Integer
Prelude> :p y3
y3 = 3
Prelude> let y4 = 344444444444444 :: Integer
Prelude> :p y4
y4 = (_t3::Integer)
chi
  • 111,837
  • 3
  • 133
  • 218