September 17, 2026

Haskell for Hobos -- Creating types: newtype

You would think this would be simple, but it ain't.

Some sources tell you the purpose of newtype is to make new types from existing types. This is only partially true. Often newtype is used in lieu of "data" to create entirely new types.

The discussion in the Haskell Wiki might be the best.

Basic use of newtype

You can use newtype much like "data", but with certain restrictions. First of all, newtype can have only one type constructor. This makes it useless for making what I might call an enumeration type. Also, newtype can have only one field in a type constructor. So both of these are illegal:
newtype Critter = Fish | Bird

newtype Dingus = Dingus {
    number :: Int
    id :: String
}
At this point you ought to be wondering what good this thing is and why it exists.

One reason is that newtype has less overhead (which makes some sense given all of the restrictions). Newtype can be used to create a new type based on an existing datatype, while changing the typeclass participation.

I'm going to leave the basics here, but just mention that there are also some distinctions involving laziness versus strictness. Newtype value constructors are strict, while "data" value constructors are lazy.

Surprises from Real World Haskell

We run into the following on page 240 of Real World Haskell

newtype Parse a = Parse {
	runParse :: ParseState -> Either String (a, ParseState)
}
This definition bears a remarkable similarity to this one:
newtype State s a = State { runState :: s -> (s, a) }
I call this the mantra of newtype magic, the famous "State" newtype. If you can wrap your head around this, you have cracked the nut of "newtype.

In many ways, there are no real surprises here. We have one type constructor with one field. The only unusual thing might be that the field holds a function.

Notice that both of these stand on their own. They are not wrapping or redefining some other type as is so often claimed for newtype.

Don't go running around (like I did) looking for a function elsewhere defining runParse or runState. A sneaky game with Haskell record syntax is going on here.

The newtype State monad

This is the single line above with the field "runState" that holds a function. Many people have puzzled over this, and some of they have written their own analysis and explanation. Here are a few of them: So, why wrap a function in a type? I'll tell you why. We want to make it participate in the Monad typeclass, that's why. The hidden part of the story are instance declarations like the following:
-- Applies a function to the internal value without
--   altering the state modification logic.
instance Functor (State s) where
    fmap f (State g) = State $ \s ->
        let (x, s') = g s
        in (f x, s')

-- Allows for independent stateful computations to be sequenced.
instance Applicative (State s) where
    pure x = State $ \s -> (x, s)

    State f_g <*> State g = State $ \s ->
        let (f, s')   = f_g s
            (x, s'')  = g s'
        in (f x, s'')

-- Sequentially pipes the resulting value and final state
--     of one action into the next.
instance Monad (State s) where
    return = pure

    (State g) >>= f = State $ \s ->
        let (x, s') = g s    -- Run the first stateful computation
            State h = f x
        in h s'              -- Run the second stateful computation

Closing thoughts

There is deep magic involved with Haskell "newtype". Haskell "data" is simple and easy and the usual way to create a new type. Haskell "type" exists just to make simple synonyms, and has no surprises.

The very existence of "newtype" in the face of these is your first hint that something unexpected is going on. Newtype has special rules.


Have any comments? Questions? Drop me a line!

Tom's software pages / tom@mmto.org