Maybe Just Nothing
Sunday, 24th May, 2009
Real World Haskell
Chapter 3: Defining types, streamlining functions
Section: Parametrised types
On the first few readings, this section (a single page in the dead tree version) flummoxed me completely. Some of the comments on the book’s web site were helpful, and now I’ve worked through it. This post can bear witness to my journey.
Contents:
- Types
- Algebraic data types
- Parametrised types
- Very Important Note
- A Use case
- Maybe, Just and Nothing
- Note also
Haskell is a strongly typed language. We can make up our own types and we define them like this:
data MyNewType = MyNewTypeGeneratorFunction Args
deriving(Show)
Where:
MyNewType
is called a type constructor. Must start with a capital letter.MyNewTypeGeneratorFunction
is called a value constructor or a data constructor. It’s an ordinary function which takesArgs
and returns an item of typeMyNewType
. Must start with a capital letter.Args
is a space separated list of components for the type.deriving(Show)
is optional. I don’t know what it is yet, but it tells the interpreter how to display the type.
This is the example given in RWH (beginning of Ch 3):
-- file: ch03/BookStore.hs data BookInfo = Book Int String [String] deriving(Show)
An algebraic data type can have more than one value constructor:
data MyPty = MyPtyGen Args | MyOtherPtyGen OtherArgs deriving(Show)
Args
and otherArgs
can be different.
The example given by RWH (section Algebraic data types, p. 45) is:
-- file: ch03/BookStore.hs type CardNumber = String type CardHolder = String type Address = [String] type CustomerID = Int data BillingInfo = CreditCard CardNumber CardHolder Address | CashOnDelivery | Invoice CustomerID deriving (Show)
Where CreditCard
, CashOnDelivery
and Invoice
are generator functions, or value constructors.
As with templates in C++, we can use type variables or parameters in data definitions:
data MyNewType a = MyNewTypeGeneratorFunction a deriving(Show)
The lower case a
is the type variable. C. E. Pramode, in a comment on the RWH webiste, and on his own RWH wiki, gives a very clear example of a parametric type:
data Complex a = Complex { real :: a, imaginary :: a }deriving(Eq,Show)Now, we can do at the ghci prompt:
let p = Complex 10 20 let q = Complex 1.2 2.3So we have p, a Complex number whose real/imag parts are integers and q, another Complex number whose real/imag parts are Doubles.
:type p Complex IntegerAnd:
:type q Complex Double
You have to have a value constructor, in other words a function which returns an item of the required type. You can’t just return the type variable itself:
data MyBogusType a = a
loading this into ghci will generate the error, Not a data constructor `a'
.
[thanks to Darrin Thompson for his comment]
Note that
data MyBogusType a = A
is fine because A
is interpreted as a value constructor.
Given the above, it might be useful to define a type that is a simple container, but that can also help us with error handling:
-- these first definitions don't work -- see Dan's comment -- why did I think they worked?! -- type WarningType = Int String -- type ErrorType = Int String type WarningType = Int type ErrorType = Int data Container a = OllKorrekt a | Warning a WarningType | Error ErrorType deriving(Show)
In ghci:
*Main> let x = OllKorrekt "qwerty" *Main> x OllKorrekt "qwerty" *Main> :type x x :: Container [Char] *Main> let y = Warning "asdasd" 13 *Main> y Warning "asdasd" 13 *Main> :type y y :: Container [Char] *Main> let z = Error (-1) *Main> z Error (-1) *Main> :type z z :: Container a *Main>
The Maybe data type, with its two value constructors, answers a similar use case. Maybe, Just and Nothing are defined in ghci’s standard library, the Prelude:
data Maybe a = Nothing | Just aThe
Maybe
type encapsulates an optional value. A value of typeMaybe a
either contains a value of typea
(represented asJust a
), or it is empty (represented asNothing
). UsingMaybe
is a good way to deal with errors or exceptional cases without resorting to drastic measures such aserror
.The
Maybe
type is also a monad. It is a simple kind of error monad, where all errors are represented byNothing
. A richer error monad can be built using theData.Either.Either
type.
error and monad we’ll meet again later.
So, Maybe is an ordinary type constructor, and Just and Nothing are ordinary value constructors, and it is the use case of encapsulating optional or exceptional values which is important.
- The Prelude defines a maybe function:
maybe :: b -> (a -> b) -> Maybe a -> b
The maybe function takes a default value, a function, and a Maybe value. If the Maybe value is Nothing, the function returns the default value. Otherwise, it applies the function to the value inside the Just and returns the result. - The module Data.Maybe defines various maybe-related functions (see also RWH section The case Expression, p. 66).
Thursday, 2nd February, 2012 at 1:53 pm
The example in the “A Use case” section needs a tweak. Maybe something like this:
type WarningType = Int
type ErrorType = Int
data Container a = OllKorrekt a
| Warning a WarningType
| Error ErrorType
deriving(Show)
I couldn’t get it working as is.
Wednesday, 8th February, 2012 at 9:43 pm
Dear Dan
Thanks for your comment.
You’re right! Sorry that got in there. I’ve changed the text.
Ivan