Type and Typeclass in Haskell¶
When I was reading the haskell aeson library implementation, I still a little confusing with type system especially for multiple constructors.
Hence, I learned it again and record some ideas here. It reviews the value constructor
, type constructor
and the strict type in type constructor.
Value Constructor¶
Written too many Go code, I'm almost lost. The True
and False
seems are values with type Bool
, but they have a name as value constructor which means they are functions. Functions are values, frankly, because they are first class. Thing need to highlight is value
is differ with function as they cannot take more parameters.
As they are functions, they can take arguments and return a value with the same type. The |
enables us to combine multiple types together (which have different constructors).
Hence, the definitions of Literal
are simple.
For Lit
, there are three value constructor without parameters.
Exclamation Mark: Laziness to Strictness¶
After learning the value constructor, we learned about it accepts parameter. Then the NumInteger Integer
should be expected, however, it's NumInteger !Integer
. What is the meaning of !
?
-- | Numbers
--
-- We preserve whether the number was integral, decimal or in scientific form.
data Number
= NumInteger !Integer -- ^ e.g. @123@
| NumDecimal !Scientific -- ^ e.g. @123.456@
| NumScientific !Scientific -- ^ e.g. @123e456@, @123e-456@ or @123.456E-967@
deriving (Eq, Show)
The !
(exclamation mark) denotes strict field, comparing with lazy field. It matters with the execution order. Under the lazy mode, the evaluation happens only when it's needed.
For example, during constructing StrictNumber
, the fib 35
will always be called no matter when the value will be used later. However, when the LazyNumber
is constructed, the fib 35
won't be called until somewhere else want to use it.
-- Lazy version
data LazyNumber = LazyNumInteger Integer
-- Strict version
data StrictNumber = StrictNumInteger !Integer
fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
lazyValue :: LazyNumber
lazyValue = LazyNumInteger (fib 35)
strictValue :: StrictNumber
strictValue = StrictNumInteger (fib 35)
Type Parameter¶
Receiving parameters by value constructor is reasonable, however, what's the meaning when the type receives some parameters like this?
-- | Array tokens.
data TkArray k e
= TkItem (Tokens (TkArray k e) e)
| TkArrayEnd k
| TkArrayErr e
deriving (Eq, Show)
Maybe
: The parameters taken by Maybe
are types, hence, we call Maybe
Type Constructor. It benefits the type when it works as boxes. As it doesn't benefit such a concrete business case: -- doesn't benefit any
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Never Add Type Constraints in Data Declaration¶
When defining a new type which receives some types, constrainting the parameters sounds great. For example, if we want to define a Map because we need to compare elements when constructing.
However, it doesn't benefit a lot, but ending up writing more class constraints, even they are useless. For example, if you want to writetoList :: Map k a -> [(k, a)]
. If you have a constraint when declaring Map, it is duplicated in toList
. Putting it in function type declarations is enough.