Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multi-parameter newtype faked with a tuple?

This is a dumb question that's been bugging me for a bit. Why can't I write a newtype with multiple parameters,

newtype A = A Int Int

while the tuple version is just fine?

newtype A = A (Int, Int)

the former is much nicer in things like pattern matching.

like image 466
gatoatigrado Avatar asked Aug 07 '11 04:08

gatoatigrado


2 Answers

newtype A = A Int creates a type isomorphic to Int. That is, it behaves exactly like Int w.r.t. e.g. bottom, but under a different name.

This is in contrast with data A = A Int which creates a lifted type that behaves differently from Int. There's another value added that is not in Int: A undefined (which is distinct from undefined::A).

Now, newtype A = A (Int, Int) creates a type isomorphic to (Int, Int). Which is, incidentally, exactly exactly what data A = A Int Int does.

So if we admit newtype A = A Int Int as being equivalent to newtype A = A (Int, Int), what do we have? newtype A = A Int Int is equivalent to newtype A = A (Int, Int) which is equivalent to data A = A Int Int.

newtype A = A Int Int is equivalent to data A = A Int Int (so newtype is redundant in this case), but

newtype A = A Int is not equivalent to data A = A Int (which is the whole point of having newtype in the first place).

So we must conclude that newtype A = A Int Int being equivalent to newtype A = A (Int, Int) creates redundancy and inconsistency, and we're better off not allowing it.

There's probably no way to give newtype A = A Int Int some other meaning which is free from these inconsistencies (or else it would be found and used I suppose ;)

like image 66
n. 1.8e9-where's-my-share m. Avatar answered Nov 11 '22 15:11

n. 1.8e9-where's-my-share m.


Because a newtype, roughly speaking, works like type at runtime and like data at compile-time. Each data definition adds an extra layer of indirection--which, under normal circumstances, means another distinct place where something can be left as a thunk--around the values it holds, whereas newtype doesn't. The "constructor" on a newtype is basically just an illusion.

Anything that combines multiple values into one, or that gives a choice between multiple cases, necessarily introduces a layer of indirection to express that, so the logical interpretation of newtype A = A Int Int would be two disconnected Int values with nothing "holding them together". The difference in the case of newtype A = A (Int, Int) is that the tuple itself adds the extra layer of indirection.

Contrast this with data A = A Int Int vs. data A = A (Int, Int). The former adds one layer (the A constructor) around the two Ints, while the latter adds the same layer around the tuple, which itself adds a layer around the Ints.

Each layer of indirection also generally adds a place where something can be ⊥, so consider the possible cases for each form, where ? stands for a non-bottom value:

  • For newtype A = A (Int, Int) : , (⊥, ?), (?, ⊥), (?, ?)

  • For data A = A Int Int : , A ⊥ ?, A ? ⊥, A ? ?

  • For data A = A (Int, Int) : , A ⊥, A (⊥, ?), A (?, ⊥), A (?, ?)

As you can see from the above, the first two are equivalent.


On a final note, here's a fun demonstration of just how newtype differs from data. Consider these definitions:

data D = D D deriving Show
newtype N = N N deriving Show

What possible values, including all possible ⊥s, do each of these have? And what do you think the two values below will be?

d = let (D x) = undefined in show x
n = let (N x) = undefined in show x

Load them in GHC and find out!

like image 40
C. A. McCann Avatar answered Nov 11 '22 15:11

C. A. McCann