Consider the definition of Maybe
:
data Maybe a = Nothing | Just a
This is a very common datatype in Haskell because it allows you to return a default Nothing
value when there isn't a sensible value to return for the intended type a.
Take the example:
ifEvenAdd2 :: Integer -> Integer
ifEvenAdd2 n =
if even n then n + 2 else ??? -- what should we put here???
Instead of promising an Integer
result, we can return Maybe Integer
.
ifEvenAdd2 :: Integer -> Maybe Integer
ifEvenAdd2 n =
if even n then Just (n + 2) else Nothing
Smart Constructors for Datatypes
Let's consider a Programmer
type which keeps track of two things, their name and age:
type Name = String
type Age = Integer
data Programmer = Programmer Name Age deriving Show
There is a problem here. We could construct a Programmer
with an empty String
or make a Programmer
with a negative age.
Let's fix it with Maybe
:
type Name = String
type Age = Integer
data Programmer = data Programmer Name Age deriving Show
mkProgrammer :: Name -> Age -> Maybe Programmer
mkProgrammer name age
| name /= "" && age >= 0 =
Just $ Programmer name age
| otherwise = Nothing
mkProgrammer
is called a smart constructor. It allows us to construct values of a type only when they meet certain criteria.
If we want to express why we didn't get a successful result back from our mkProgrammer
constructor, we can use the Either
datatype.
data Either a b = Left a | Right b
Let's make a sum type to enumerate our possible failure modes:
data ProgrammerInvalid =
NameEmpty
| AgeTooLow
deriving (Eq, Show)
The constructor type changes to:
mkProgrammer :: Name -> Age -> Either ProgrammerInvalid Programmer
Logic changes:
type Name = String
type Age = Integer
data Programmer = Programmer Name Age deriving SShow
data ProgrammerInvalid =
NameEmpty
| AgeTooLow
deriving (Eq, Show)
mkProgrammer :: Name -> Age -> Either ProgrammerInvalid Programmer
mkProgrammer name age
| name /= "" && age >= 0 =
Right $ Programmer name age
| name == "" = Left NameEmpty
| otherwise = Left AgeTooLow
Left
is used as the invalid or error constructor for a couple of reasons.
For one, it is conventional to do so in Haskell.
Since you normally want to apply functions and map over the case that doesn't stop your program, it has become convention that the Left
of Either
is used for whatever case is going to cause the work to stop.
Haskell has higher-kinded types.
Type constructors (higher-kinded types) are types that take more types as arguments.
data Example a = Blah | Roof | Woot a
Example
is a type construct because it takes a type argument a
which is used with the Woot
data constructor.
Querying kinds:
Prelude> :k Example
Example :: * -> *
Example
has one parameter, so it must be applied to one type in order to become a concrete type represented by a single *
.
The two-tuple takes two arguments, so it must be applied to two types to become a concrete type:
Prelude> :k (,)
(,) :: * -> * -> *
Data Constructors are Functions
Data constructors are curried just like functions are.
Nullary data constructors, which are values taking no arguments, are not like functions.
However, data constructors that do take arguments behave like functions.
Like functions, their arguments are type-checked against the specification in the type.
What's the benefit of the Nothing constructor in Haskell? - https://softwareengineering.stackexchange.com/questions/176576/whats-the-benefit-of-the-nothing-constructor-in-haskell
Data.Maybe - https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Maybe.html
Haskell Book - https://haskellbook.com/