Skip to content

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 newtypes, 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.