In Haskell you can define a algebraic data type without a constructor:
data Henk
But what is the purpose of a type (or kind?) that doesn't have a constructor?
Algebraic types have the opposite intent: they provide a structure for data, to enable any number of functions to be written which work with that data. A major benefit of algebraic data types is that the structure of algorithms which work on them tends to mirror the structure of the types themselves.
A general algebraic data type is a possibly recursive sum type of product types. Each constructor tags a product type to separate it from others, or if there is only one constructor, the data type is a product type. Further, the parameter types of a constructor are the factors of the product type.
This is a type where we specify the shape of each of the elements. Wikipedia has a thorough discussion. "Algebraic" refers to the property that an Algebraic Data Type is created by "algebraic" operations. The "algebra" here is "sums" and "products": "sum" is alternation ( A | B , meaning A or B but not both)
Wikipedia's list of programming languages with algebraic data types (ADTs) suggests that Rust indeed has ADTs, but I am not sure if my interpretation for why this is true for Rust is correct. As I understand it, to say that one has ADTs one needs to have: Values: Zero (or Void) - ! in Rust) - a type that gets no value.
Type-level machinery often requires types to exist but never constructs values of such types.
E.g., phantom types:
module Example (Unchecked, Checked, Stuff()) where
data Unchecked
data Checked
data Stuff c = S Int String Double
check :: Stuff Unchecked -> Maybe (Stuff Checked)
check (S i s d) = if i>43 && ...
then Just (S i s d)
else Nothing
readFile :: Filepath -> IO (Stuff Unchecked)
readFile f = ...
workWithChecked :: Stuff Checked -> Int -> String
workWithChecked stuff i = ...
workWithAny :: Stuff any -> Int -> Stuff any
workWithAny stuff i = ...
As long as the S
constructor is not exported by the module, the user of this library can not forge the "checked" status on the Stuff
data type.
Above, the workWithChecked
function does not have to sanitize the input every time it is called. The user must have already done it, since it has to provide a value in the "checked" type -- and this means the user had to call the check
function beforehand. This is an efficient and robust design: we do not repeat the same check over and over at every call, and yet we do not allow the user to pass unchecked data.
Note how the values of types Checked
,Unchecked
are immaterial -- we never use those.
As others mentioned in comments, there are many other uses of empty types than phantom types. For instance, some GADTs involve empty types. E.g.
data Z
data S n
data Vec n a where
Nil :: Vec Z a
Cons :: a -> Vec n a -> Vec (S n) a
Above we use empty types to record length information in types.
Further, types with no constructors are needed to achieve some theoretical properties: if we look for an a
such that Either a T
is isomorphic to T
, we want a
to be empty. In type theory, an empty type is commonly used as a type equivalent of a logically "false" proposition.
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