Newtype wrappers

Chris Dornan chris at chrisdornan.com
Tue Jan 15 01:20:33 CET 2013

```Looks great; I care and have no improvements to offer; +1 from me.

Chris

From:  Simon Peyton-Jones <simonpj at microsoft.com>
Date:  Monday, 14 January 2013 18:09
Subject:  Newtype wrappers

Friends

I¹d like to propose a way to ³promote² newtypes over their enclosing type.
Here¹s the writeup

Any comments?  Below is the problem statement, taken from the above page.

I¹d appreciate
·        A sense of whether you care. Does this matter?

·        Improvements to the design I propose

Simon

The problem
Suppose we have
newtype Age = MkAge Int
Then if n :: Int, we can convert n to an Age thus: MkAge n :: Age. Moreover,
this conversion is a type conversion only, and involves no runtime
instructions whatsoever. This cost model -- that newtypes are free -- is
important to Haskell programmers, and encourages them to use newtypes freely
to express type distinctions without introducing runtime overhead.

Alas, the newtype cost model breaks down when we involve other data
structures. Suppose we have these declarations
data T a   = TLeaf a     | TNode (Tree a) (Tree a)
data S m a = SLeaf (m a) | SNode (S m a) (S m a)
and we have these variables in scope
x1 :: [Int]
x2 :: Char -> Int
x3 :: T Int
x4 :: S IO Int
Can we convert these into the corresponding forms where the Int is replaced
by Age? Alas, not easily, and certainly not without overhead.
* For x1 we can write map MkAge x1 :: [Age]. But this does not follow the
newtype cost model: there will be runtime overhead from executing the map at
runtime, and sharing will be lost too. Could GHC optimise the map somehow?
This is hard; apart from anything else, how would GHC know that map was
special? And it it gets worse.
* For x2 we'd have to eta-expand: (\y -> MkAge (x2 y)) :: Char -> Age. But
this isn't good either, because eta exapansion isn't semantically valid (if
x2 was bottom, seq could distinguish the two). See #7542
<http://hackage.haskell.org/trac/ghc/ticket/7542>  for a real life example.
* For x3, we'd have to map over T, thus mapT MkAge x3. But what if mapT
didn't exist? We'd have to make it. And not all data types have maps. S is a
harder one: you could only map over S-values if m was a functor. There's a
lot of discussion abou this on #2110