Cogs and Levers A blog full of technical stuff

type, newtype & data

Introduction

This post is just a small write up on the differences between these three keywords and their uses within Haskell.

type

Using the type keyword is the same as just referring to the actual type that you’ve declared. In other words, type just lets you make a synonym for the original type.

-- a deck is an array of cards
type Deck = [Card]

-- an array of crows would be a murder
type Murder = [Crow]

Using these, we’ll be able to refer to a Card list as a Deck. They can be used interchangeably as they’ll directly cast. All this really does for us, is gives a pre-existing type a more meaningful name to our code.

newtype

Using the newtype keyword, we make a thin wrapper around an existing type. Something that will be treated differently at compile time, but will be directly translatable (and not subject to conversion) at runtime.

-- declare the data type
newtype Word = Word { getWord :: String }
  derives (Show, Eq)

-- using the data type
let helloWord = Word "Hello"
let byeWord = Word "Bye"
let greetingWord = Word "Hello"

newtype only allows you one constructor and one field. It’s important that its used when you’re creating data entries that have these constraints on them. These attributes make newtype a great candidate for when you just want to add a typeclass instance to an existing type.

data

The data keyword allows you to build much more complex types. With the data keyword, you can have as many constructors and fields as you like.

-- create a person data type
data Person = Person String String Int deriving (Show)
let john = (Person "John" "Smith" 35)

-- create a more complex employee type
data Employee = Employee Int Person deriving (Show)
let john = (Employee 6 (Person "John" "Smith" 35))

Of course, it would make more sense to use “record syntax” when defining these datatypes above.

-- define a person
data Person = Person {firstName :: String
                     ,lastName :: String
                     ,age :: Int} deriving (Show)

-- define an employee
data Employee = Employee {employeeId :: Int
                         ,person :: Person} deriving (Show)

-- define "john"
let john = Employee {employeeId = 6
                    ,person = (Person {firstName = "John"
                                      ,lastName = "Smith"
                                      , age = 35}) 
                    }

Wrapping up

  • Use type to give your types more meaningful names
  • Use newtype if you just want to take an existing type and add a typeclass instance to it
  • Use data if you want to create your own datatype