Haskell enables one to construct algebraic data types using type constructors and data constructors. For example,
data Circle = Circle Float Float Float
and we are told this data constructor (Circle on right) is a function that constructs a circle when give data, e.g. x, y, radius.
Circle :: Float -> Float -> Float -> Circle
My questions are:
What is actually constructed by this function, specifically?
Can we define the constructor function?
I've seen Smart Constructors but they just seem to be extra functions that eventually call the regular constructors.
Coming from an OO background, constructors, of course, have imperative specifications. In Haskell, they seem to be system-defined.
In Haskell, without considering the underlying implementation, a data constructor creates a value, essentially by fiat. “ ‘Let there be a
Circle’, said the programmer, and there was a
Circle.” Asking what
Circle 1 2 3 creates is akin to asking what the literal
1 creates in Python or Java.
A nullary constructor is closer to what you usually think of as a literal. The
Boolean type is literally defined as
data Boolean = True | False
False are data constructors, not literals defined by Haskell grammar.
The data type is also the definition of the constructor; as there isn't really anything to a value beyond the constructor name and its arguments, simply stating it is the definition. You create a value of type
Circle by calling the data constructor
Circle with 3 arguments, and that's it.
A so-called smart constructor is just a function that calls a data constructor, with perhaps some other logic to restrict which instances can be created. For example, consider a simple wrapper around
newtype PosInteger = PosInt Integer
The constructor is
PosInt; a smart constructor might look like
mkPosInt :: Integer -> PosInteger mkPosInt n | n > 0 = PosInt n | otherwise = error "Argument must be positive"
mkPosInt, there is no way to create a
PosInteger value with a non-positive argument, because only positive arguments actually call the data constructor. A smart constructor makes the most sense when it, and not the data constructor, is exported by a module, so that a typical user cannot create arbitrary instances (because the data constructor does not exist outside the module).