Here is my code:
data TC a = DC1 a | DC2 a
getDC :: TC a -> String
getDC (DC1 x) = "created by first data constructor"
getDC (DC2 x) = "created by second data constructor"
In Hugs:
Main> getDC (DC1 10)
"created by first data constructor"
Main> getDC (DC2 10)
"created by second data constructor"
So, the interpreter can determine which data constructor being used. As i know, every value has an associated type. Let's check:
Main> :t (DC1 10)
DC1 10 :: Num a => TC a
Main> :t (DC2 10)
DC2 10 :: Num a => TC a
Only type constructor (TC) can be seen there.
Why, where and how does interpreter hold additional information about data constructor?
While types provide important compile time information you're still manipulating values at runtime. The constructor used is just a property of the value—in particular, you use pattern matching to determine the choice of constructor used in a value.
getDC :: TC a -> String
getDC (DC1 x) = "created by first data constructor"
getDC (DC2 x) = "created by second data constructor"
-- or, to be more clear about the pattern matching
getDC :: TC a -> String
getDC dc = case dc of
(DC1 x) -> "created by first data constructor"
(DC2 x) -> "created by second data constructor"
To attempt a little more clarity, let's try making the natural numbers instead of using the abstract TC type.
data Nat = Zero | Succ Nat
In other words, what we write as 0 could be represented as a Nat
as
Zero :: Nat
and what we write as 3 could be represented as
Succ (Succ (Succ Zero))
We could write a function on `Nat
isThree :: Nat -> Bool
isThree (Succ (Succ (Succ Zero))) = True
isThree _ = False
and the behavior of this function isn't indicated in its type (Nat -> Bool
), so instead the behavior must be performed by the value. Indeed, we use pattern matching to destruct the value and enforce the behavior.
Think of values as boxes containing a tag field that identifies which data constructor was used to create it, as well as the additional arguments required by that constructor. Since Haskell is statically typed, the runtime doesn't have to distinguish between values of different types, so the tag only needs to identify the data constructor within that type. You can only examine the tag indirectly via pattern matching, but knowing the type, and the tag, you can determine the data constructor, and can safely access the arguments.
Note, if a type has only one data constructor, then values of that type do not require a tag.
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