For a uni assignment, we have been given a line of Haskell code which shows:
newtype TC a = TC ([Id] -> Either TypeError ([Id], a))
Firstly, TypeError
is something which needs to be implemented by us for the assignment so I can't post the data declaration here, but my question is this. How do I read the code above? What is the a
right after the newtype TC
? I also don't understand how TC
is being reused to the right of the equals sign.
I think a
here is a type variable since newtype
works similarly to data. I don't know how knowing this will help my understanding.
What is the
a
right after thenewtype TC
?
The a
in the newtype declaration
newtype TC a = ...
expresses much the same as the x
in a function declaration
f x = ...
a
is a type parameter. So you'll be able to use TC
as, for example, TC Int
or TC Bool
, similar to how you're able to use f
like f 1
or f "bla bla"
(depending on its type).
The case TC Int
is equivalent to the following alternative:
newtype TCInt = TCInt ([Id] -> Either TypeError ([Id], Int))
I also don't understand how
TC
is being reused to the right of the equals sign.
That is a bit of a confusing quirk in Haskell. Actually TC
is not reused, rather you're declaring two separate entities which are both called TC
. You could also call them differently:
newtype TC_T a = TC_V ([Id] -> Either TypeError ([Id], a))
TC_T
is a type constructor. This is the thing that will appear in type signatures, i.e. TC_T Int
or TC_T Bool
.TC_V
is a value constructor. You use this when, well, generating values of type TC_T Int
or TC_T Bool
.So for example, you might write these:
tci :: TC_T Int
tci = TC_V (\ids -> Right (ids, 37))
tcb :: TC_T Bool
tcb = TC_V (\ids -> Right (reverse ids, False))
With your original version it looks like this:
tci :: TC Int
tci = TC (\ids -> Right (ids, 37))
tcb :: TC Bool
tcb = TC (\ids -> Right (reverse ids, False))
...but it's still two separate things both called TV
here. Most newtypes in Haskell call the type- and value constructors the same, but often only the type constructor is exported from a module and the value constructor left as an implementation detail.
Most of this is the same for data
as for newtype
. You could as well have
data TC a = TC ([Id] -> Either TypeError ([Id], a))
... the only difference to the newtype version is a subtle indirection: if it's data
, then the compiler inserts an indirection which allows a bit more lazyness, but in this case there's hardly a point to doing that.
In practice, you generally use data
only when you need multiple constructors and/or a constructor with multiple fields, which newtype
does not support.