Newtype
We have discussed the data
keyword at length, as a means to define new data
types. There exists a specialized version of data
, called newtype
. It has
some serious restrictions:
- A type defined using
newtype
must have exactly one data constructor. - This data constructur must take exactly one argument.
So, Bool
cannot be defined using newtype
, because it has two data
constructors:
data Bool = False | True
Point2D
also cannot be defined using newtype
. It has one data constructor,
but this data constructor takes two arguments:
data Point2D = P Double Double
How about this type then:
newtype BannerNumber = B { intFromBanner :: Int }
This works, because there is only one data constructor, B
, and it takes one
argument of type Int
. And, yes, we can use record syntax with newtype
. We
don't have to though. The following would also be fine:
newtype BannerNumber = B Int
We can also define parameterized newtype
s, again as long as there's a single
argument to the data constructor:
newtype Pair a b = Pair (a, b)
This one was sneaky. You may think that Pair
(the data constructor) takes
two arguments, but it doesn't. It takes a single argument, which is a pair whose
first component has type a
and whose second component has type b
. Note that
it is okay for the type constructor to take more than one argument. It is the
data constructor that must take exactly one argument.
What's the point of defining things like BannerNumber
and Pair
? And why
don't we just use data
to define them?
The answer to the first question is type safety. By having a special type to
represent banner numbers, for example, the compiler does not let us accidentally
do arithmetic with banner numbers or pass the number of students at Dalhousie,
an Int
presumably, as an argument to a function or data constructor that
expects a BannerNumber
. Int
and BannerNumber
are different types.
Given that defining new types in Haskell is a breeze, you should use custom
types to represent the semantics of your program wherever you can. A
BannerNumber
isn't just any old number; it's a number that identifies a
student. You want to help the compiler catch as many errors in your code as
possible before your code even runs.
So why not data BannerNumber = ...
then? The reason is runtime efficiency.
That's where the restrictions on types defined using newtype
come in. When
storing a value of some type defined using data
, the runtime system generally
needs to store information about which data constructor was used to construct
this value, and we need to store the values that were passed to the data
constructor as arguments. Storing the data constructor itself is necessary to
support pattern matching. Now remember, we can define a type using newtype
only if it has exactly one data constructor and this data constructor has
exactly one argument. Thus, there is no need to store the data constructor:
there is only one data constructor we could possibly have used to construct this
value. Therefore, we can use the same runtime representation for a newtype as is
used for the one argument of its data constructor. In C, we would most likely
represent a banner number as an int
. In Haskell, we get the safety benefits of
defining a separate BannerNumber
type that is distinct from Int
, but the
runtime representations of BannerNumber
and Int
are exactly the same; there
is no runtime overhead in manipulating a BannerNumber
.
What's somewhat unclear to me is why we need the newtype
keyword. Surely, the
compiler could be intelligent enough to apply the newtype optimization to any
data type defined using data
and which has only one data constructor, which
takes one argument. I'm sure there is a good reason why this optimization is
undesirable in some corner cases, so we are forced to choose between wanting
this optimization (by using newtype
) or not (by using data
). I just don't
know what it is.