robb.re

Newtypes

Posted on August 27, 2015

As far as usage and syntax go, newtype is basically the same as data except that a newtype can have only one field and one constructor. Easy enough.

For me, the part that took some time to sink in is that it’s possible for the field to be a function.

λ> newtype Foo s a = Foo { runFoo :: s -> (s, a) }

It’s important to note that the right hand side defines the constructor of the type. In this case the constructor takes a function that takes a single arg s and returns a tuple (s, a).

λ> :t Foo
Foo :: (s -> (s, a)) -> Foo s a

This matches the way that data constructors work - the right hand side defines the constructor function. This is the part that caused me some confusion. I knew how to define and use data but the penny hadn’t fully dropped that the parameters on the left hand side were not used to define the constructor. I thought of the right hand side as purely for defining the accessor functions.

λ> data A a b = A {aGetter :: String,  bGetter :: String}
λ> :t A
A :: String -> String -> A a b

In hindsight this is obvious but isn’t that always the case?

Using the constructor

As a dumb example we can create a silly function that makes a tuple and use that in the constructor

λ> let makeTuple mul a = (a * mul, a * mul +2)
λ> let s = Foo (makeTuple 34)
λ> :t s
s :: Num a => Foo a a

We now have an instance of the Foo type wrapped around our (partially applied) makeTuple function. If we compare Foo and runFoo we can see that that mirror each other and can be used to wrap and unwrap the function s -> (s, a)

λ> :t Foo
Foo :: (s -> (s, a)) -> Foo s a
λ> :t runFoo
runFoo :: Foo s a -> s -> (s, a)

Note that when applying runFoo to s (our Foo instance) we can see that we need one more argument to be passed in order to get back the tuple.

λ> :t runFoo s
runFoo s :: Num a => a -> (a, a)
λ> print $ runFoo s 3
(102,104)

and the one line version

λ> print $ runFoo (Foo $ makeTuple 10) 4
(40,42)