Difference between `data` and `newtype` in Haskell

haskell, newtype, type-systems, types

Solution

Great question!

There are several key differences.

Representation

- A `newtype` guarantees that your data will have exactly the same representation at runtime, as the type that you wrap.

- While `data` declares a brand new data structure at runtime.

So the key point here is that the construct for the `newtype` is guaranteed to be erased at compile time.

Examples:

- `data Book = Book Int Int`

- `newtype Book = Book (Int, Int)`

Note how it has exactly the same representation as a `(Int,Int)`, since the `Book` constructor is erased.

- `data Book = Book (Int, Int)`

Has an additional `Book` constructor not present in the `newtype`.

- `data Book = Book {-# UNPACK #-}!Int {-# UNPACK #-}!Int`

No pointers! The two `Int` fields are unboxed word-sized fields in the `Book` constructor.

Algebraic data types

Because of this need to erase the constructor, a `newtype` only works when wrapping a data type with a single constructor. There's no notion of "algebraic" newtypes. That is, you can't write a newtype equivalent of, say,

data Maybe a = Nothing
             | Just a

since it has more than one constructor. Nor can you write

newtype Book = Book Int Int

Strictness

The fact that the constructor is erased leads to some very subtle differences in strictness between `data` and `newtype`. In particular, `data` introduces a type that is "lifted", meaning, essentially, that it has an additional way to evaluate to a bottom value. Since there's no additional constructor at runtime with `newtype`, this property doesn't hold.

That extra pointer in the `Book` to `(,)` constructor allows us to put a bottom value in.

As a result, `newtype` and `data` have slightly different strictness properties, as explained in the Haskell wiki article.

Unboxing

It doesn't make sense to unbox the components of a `newtype`, since there's no constructor. While it is perfectly reasonable to write:

data T = T {-# UNPACK #-}!Int

yielding a runtime object with a `T` constructor, and an `Int#` component. You just get a bare `Int` with `newtype`.

References:

- "Newtype" on the Haskell wiki

- Norman Ramsey's answer about the strictness properties

Problem

What is the difference when I write this? ``` data Book = Book Int Int ``` versus ``` newtype Book = Book (Int, Int) -- "Book Int Int" is syntactically invalid ```

Original source

Related problems