Reading one of my Haskell books, I came across the sentence:
Data declarations always create a new type constructor, but may or may not create new data constructors.
It sounded strange that one should be able to declare a data type with no data constructor, because it seems that then one could never instantiate the type. So I tried it out. The following data declaration compiles without error.
data B = String
How would one create an instance of this type? Is it possible? I cannot seem to find a way.
I thought maybe a data constructor with name matching the type constructor would be created automatically, but that does not appear to be the case, as shown by the error resulting from attempting to use B
as a data constructor with the declaration in scope.
Prelude> data B = String deriving Show
Prelude> B
<interactive>:129:1: error: Data constructor not in scope: B
Why is this data declaration permitted to compile if the type can never be instantiated? Is it permitted solely for some formal reason despite not having a known practical application?
I also wonder whether my book's statement about data types with no constructor might be referring to types declared via the type
or newtype
keywords instead of by data
.
In the type
case, type synonyms clearly do not use data
constructors, as illustrated by the following.
Prelude> type B = String
Prelude>
Type synonyms such as this can be instantiated by the constructors of the type they are set to. But I am not convinced that this is what my book is referring to as type synonyms do not seem to be declaring a new data type as much as simply defining an new alias for an existing type.
In the newtype
case, it appears that types without data
constructors cannot be created, as shown by the following error.
Prelude> newtype B = String
<interactive>:132:13: error:
• The constructor of a newtype must have exactly one field
but ‘String’ has none
• In the definition of data constructor ‘String’
In the newtype declaration for ‘B’
type
and newtype
do not appear to be what the book is referring to, which brings me back to my original question: why it is possible to declare a type using data
with no data constructor?
How would one create an instance of this type?
The statement from your book is correct, but your example is not. data B = String
defines a type constructor B
and a data constructor String
, both taking no arguments. Note that the String
you define is in the value namespace, so is different from the usual String
type constructor.
ghci> data B = String
ghci> x = String
ghci> :t x
x :: B
However, here is an example of a data definition without data constructors (so it cannot be instantiated).
ghci> data B
Now, I have a new type constructor B
, but no data constructors to produce values of type B
. In fact, such a data type is declared in the Haskell base
: it is called Void
:
ghci> import Data.Void
ghci> :i Void
data Void -- Defined in ‘Data.Void’
Why is this data declaration permitted to compile if the type can never be instantiated?
Being able to have uninhabited types turns out to be useful in a handful of places. The examples that I can think of right now are mostly passing in such a type as a type parameter to another type constructor. One more practical use case is in streaming libraries like conduit
.
There is a ConduitM i o m r
type constructor where: i
is the type of the input stream elements, o
the type of the output stream elements, m
the monad in which actions are performed, r
is the final result produced at the end.
Then, it defines a Sink
as
type Sink i m r = ConduitM i Void m r
since a Sink
should never output any values. Void
is a compile time guarantee that Sink
cannot output any (non-bottom) values.
Much like Identity
, Void
is mostly useful in conjunction with other abstractions.
... type synonyms clearly do not use data constructors
Yes, but they are not defining type constructors either. Synonyms are just some surface-level convenience renaming. Under the hood, nothing new is defined.
In the newtype case, it appears that types without data constructors cannot be created, as shown by the following error.
I suggest you look up what newtype
is for. The whole point of newtype
is to provide a zero-cost wrapper around an existing type. That means you have one and exactly one constructor taking one and exactly one argument (the wrapped value). At compile time, the wrapping and unwrapping operations become NOPs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With