What do Haskell (data) constructors construct?

• A+
Category：Languages

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:

1. What is actually constructed by this function, specifically?

2. 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

where True and 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 Integer:

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"

With 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).