Chapter 4: Basic datatypes¶
4.1 Basic datatype¶
In this chapter, we will:
review types we have seen in previous chapters;
learn about datatypes, type constructors, and data constructors;
work with predefined datatypes;
introduce control flow with if-then-else expressions;
learn more about type signatures and a bit about type classes.
4.2 What are types?¶
Every value has an associated type.
Types are groups of values that share something in common.
In Haskell values and types live in different namespaces: term-level and type-level.
4.3 Anatomy of a data declaration¶
You can examine the type of an expression with :type
.
You can examine the definition of a datatype with :info
.
Here is a simple datatype declaration:
-- OR
-- v
data Bool = False | True
-- ^ ^ ^
-- type \ |
-- constructor data constructors
The type constructor is the name of the type. It lives in the type level.
Data constructors are either concrete values or functions to construct concrete values, and lives in the term level.
You can have a type constructor and data constructor with the same name, and it will be unambiguous, because they live in different namespaces (type level and term level).
4.3.1 Exercises: Mood Swing¶
Given this datatype, answer the following questions:
data Mood = Blah | Woot deriving Show
What is the type constructor, or name of this type?
Mood
If the function requires a Mood value, what are the values you could possibly use?
Blah
orWoot
We’re writing a function called
changeMood
, and so far it has the type signaturechangeMood :: Mood -> Woot
. What’s wrong with that?Woot
is a data constructor, and not a type constructor, or type name. In other words,Woot
is a value, and not a type. The signature should be written aschangeMood :: Mood -> Mood
, instead.
Fix any mistakes and complete the function:
changeMood Mood = Woot changeMood _ = Blah
This should instead be:
changeMood Blah = Woot changeMood Woot = Blah
Enter all of the above into a source file, load it into ghci, and run it to make sure you got it right.
4.4 Numeric types¶
Integral numbers¶
Theses are whole numbers, positive and negative.
Int
A fixed-precision integer type with at least the range -2^29..2^29-1. The exact range for a given implementation can be determined by using
minBound
andmaxBound
from thePrelude.Bounded
class.·∾ 1 :: Int 1 ·∾ minBound :: Int -9223372036854775808 ·∾ maxBound :: Int 9223372036854775807
Integer
Arbitrarily large (or small) numbers.
·∾ minBound :: Integer <interactive>:33:1: error: • No instance for (Bounded Integer) arising from a use of ‘minBound’ • In the expression: minBound :: Integer In an equation for ‘it’: it = minBound :: Integer
Word
An unsigned integral type, with the same size as Int. The smallest number is 0. Word is suitable when you want to express whole digits that don’t include negative numbers.
Fractional¶
Float
Single-precision floating point numbers. Floating point can shift how many bits it uses to represent numbers before or after the decimal point.
The implementation details of how this happens can lead to some weird behaviour. For example
0.3 + 0.3 + 0.3
=0.8999999999999999
, rather than.9
. Because of the potential to lose precision, it’s probably a good idea to use other types where possible.
Double
Double-precision floating point numbers. It has twice as many bits to describe numbers as
Float
does.
Rational
A fractional number that represents a ratio of two
Integer
’s.Rational
is arbitrarily precise but not as efficient asScientific
.You can use it like:
·∾ 3 / 4 :: Rational 3 % 4Some fractional types have a instance of
Real
, which providestoRational
. Using it, you can do conversions:·∾ toRational 3.8 4278419646001971 % 1125899906842624There is also a more general datatype called
Ratio
that allows a ratio of any twoIntegral
types, rather thanInteger
types specifically. You can use it like:·∾ import Data.Ratio ·∾ 3 % 8 3 % 8
Fixed
A fixed precision type that provides factional types of predefined resolution:
·∾ import Data.Fixed ·∾ Fixed E E0 E1 E12 E2 E3 E6 E9 ·∾ 0.01 :: Fixed E2 0.01 ·∾ (0.3 :: Fixed E2) + 0.3 + 0.3 0.90
Scientific
Almost arbitrary precision. The coefficient is an
Integer
and the exponent is anInt
.This type comes from the scientific package.
An example:
❯❯❯ stack ghci --package scientific ·∾ import Data.Scientific ·∾ scientific 8 20 8.0e20 ·∾ scientific 8 20 + scientific 99 3 8.00000000000000099e20
All of these numeric types have an instance of a type class
called Num
, which provides +
, -
, and *
. This
means that those operations are polymorphic:
·∾ 8 * (8 :: Float)
64.0
·∾ 8 * (8 :: Word)
64
However, if you give them mismatched types, you’ll still have to explicitly convert one of them:
·∾ (8 :: Integer) * (8 :: Int)
<interactive>:8:19: error:
• Couldn't match expected type ‘Integer’ with actual type ‘Int’
• In the second argument of ‘(*)’, namely ‘(8 :: Int)’
In the expression: (8 :: Integer) * (8 :: Int)
In an equation for ‘it’: it = (8 :: Integer) * (8 :: Int)
4.5 Comparing values¶
Haskell has the standard relational operators
==
,/=
,<
,>
.Greater than and less than also check ordering, and are part of the Ord typeclass.
==
and/=
are part of the Eq typeclass.
4.6 Go on and Bool me¶
&&
logical and||
logical ornot
The if-then-else control flow construct is an expression in Haskell, not a statement.
if condition then expression else expression
, where expression must have the same type in both arms.
4.6.1 Exercises: Find the Mistakes¶
not True && true
should benot True && True
not (x = 6)
should benot (x == 6)
(1 * 2) > 5
[Merry] > [Happy]
should be"Merry" > "Happy"
, unlessMerry
andHappy
are data constructors.[1, 2, 3] ++ "look at me!"
should be"123" ++ "look at me!"
4.7 Tuples¶
Tuples are heterogeneous structures which contain a fixed number of values.
·∾ :type (1,'o',True)
(1,'o',True) :: Num a => (a, Char, Bool)
·∾ :type (1,"aletheia")
(1,"aletheia") :: Num a => (a, [Char])
They are actually a family of types. There is a type constructor and data
constructor for each arity, up to 32 arguments, defined in GHC.Tuple
.
·∾ :info (,)
data (,) a b = (,) a b -- Defined in ‘GHC.Tuple’
·∾ :info (,,,)
data (,,,) a b c d = (,,,) a b c d -- Defined in ‘GHC.Tuple’
You’ll often hear tuples of different arities described as n-tuple, or
three-tuple, or triple, or pair, or unit (for the empty tuple ()
).
Each tuple constructor has implementations of different type classes that permit operations on them, but sometimes the behaviour is unexpected:
·∾ length (1,2)
1
·∾ fmap (+3) (1,2)
(1,5)
·∾ fmap (+3) (1,2,3)
<interactive>:10:1: error:
• Non type-variable argument in the constraint: Functor ((,,) a b1)
(Use FlexibleContexts to permit this)
• When checking the inferred type
it :: forall a b1 b2.
(Functor ((,,) a b1), Num b2, Num a, Num b1) =>
(a, b1, b2)
Personally I find this enough of a foot-gun to avoid using tuples.
Here are some useful functions for tuples, which come from Data.Tuple
:
·∾ :type fst
fst :: (a, b) -> a
·∾ fst (1,2)
1
·∾ :type snd
snd :: (a, b) -> b
·∾ snd (1,2)
2
·∾ :info curry
curry :: ((a, b) -> c) -> a -> b -> c -- Defined in ‘Data.Tuple’
·∾ curry fst 1 2
1
·∾ :type uncurry
uncurry :: (a -> b -> c) -> (a, b) -> c
·∾ uncurry (+) (1,2)
3
·∾ import Data.Tuple
·∾ :type swap
swap :: (a, b) -> (b, a)
·∾ swap (1,2)
(2,1)
When should you use tuples over records, or Map
’s (dictionaries), or some
other data structure?
4.8 Lists¶
Lists in Haskell are similar to singly-linked lists, but they can be of infinite length. All elements of the list must be of the same type.
4.9 Chapter Exercises¶
For these exercises, you’ll need these definitions in scope at the repl:
awesome = ["Papuchon", "curry", ":)"]
also = ["Quake", "The Simons"]
allAwesome = [awesome, also]
4.9.1 Untitled¶
Here is a terminal recording where I answer all of the exercises. The answers below are mostly copy-pasted from that, with the exception of 9 and 10, which were incorrect in the recording.
What do you think the type signature of
length
is?length :: Num b => [a] -> b
was my guess. It turns out that it’slength :: Foldable t => t a -> Int
, instead.
What are the results of the following expressions?
length [1,2,3,4,5]
–>5
length [(1,2),(2,3),(3,4)]
–>3
length allAwesome
–>2
length (concat allAwesome)
–>5
Determine which expression of
6 / 3
and6 / length [1,2,3]
will throw an error and explain why:·∾ :type (/) (/) :: Fractional a => a -> a -> a ·∾ :type length length :: Foldable t => t a -> Int ·∾ 6 / 3 -- this should work 2.0 ·∾ 6 / length [1,2,3] -- (/) will expect a Fractional, but get an Int from length ... This should fail with a type error <interactive>:24:1: error: • No instance for (Fractional Int) arising from a use of ‘/’ • In the expression: 6 / length [1, 2, 3] In an equation for ‘it’: it = 6 / length [1, 2, 3] ·∾ 6 / fromIntegral (length [1,2,3]) 2.0
How can you fix the code from the previous question using a different division function/operator?
·∾ 6 `div` length [1,2,3] -- div operates on Integrals instead of Fractionals, so this should work 2
What is the type of the expression
2 + 3 == 5
? What would we expect as a result?·∾ -- 2 + 3 == 5 should have the type of Bool, I think ·∾ :type 2 + 3 == 5 2 + 3 == 5 :: Bool ·∾ 2 + 3 == 5 -- This should evaluate to True True
What are the type and expected result values of the following:
·∾ x = 5 ·∾ x + 3 == 5
Here:
·∾ x = 5 ·∾ :type x x :: Num p => p ·∾ :type x + 3 == 5 x + 3 == 5 :: Bool ·∾ x + 3 == 5 False
Which of the following expressions will work, and why?
·∾ length allAwesome == 2 -- True True ·∾ length [1,'a',3,'b'] -- type error, lists members must be of the same type <interactive>:45:9: error: • No instance for (Num Char) arising from the literal ‘1’ • In the expression: 1 In the first argument of ‘length’, namely ‘[1, 'a', 3, 'b']’ In the expression: length [1, 'a', 3, 'b'] ·∾ length allAwesome + length awesome -- this should work 5 ·∾ (8 == 8) && ('b' < 'a') -- True && False => False False ·∾ -- (&&) needs Bool, but will get a number ·∾ (8 == 8) && 9 <interactive>:48:13: error: • No instance for (Num Bool) arising from the literal ‘9’ • In the second argument of ‘(&&)’, namely ‘9’ In the expression: (8 == 8) && 9 In an equation for ‘it’: it = (8 == 8) && 9
Write a palindrome function, using
reverse
:·∾ isPalindrome x = x == reverse x ·∾ isPalindrome "racecar" True ·∾ isPalindrome "umbrella" False ·∾ isPalindrome "bob" True ·∾ isPalindrome [1,2,3,2,1] True
Write a function to return the absolute value of a number using if-then-else:
·∾ myAbs x = if signum x == (-1) then negate x else x ·∾ myAbs 8 8 ·∾ myAbs (-8) 8 ·∾ myAbs (-20) 20 ·∾ myAbs 0 0
Fill in the definition of the function
f :: (a, b) -> (c, d) -> ((b, d), (a, c))
usingfst
andsnd
:·∾ :{ ·∾ f :: (a,b) -> (c,d) -> ((b,d),(a,c)) ·∾ f x y = ((snd x, snd y),(fst x, fst y)) ·∾ :} ·∾ f (1,2) (3,4) ((2,4),(1,3))
4.9.2 Correcting syntax¶
Correct the code and try it out on GHCi.
We want a function that returns the length of a string + 1.
Original:
x = (+) F xs = w 'x' 1 where w = length xs
Corrected:
·∾ x = (+) ·∾ f l = x w 1 where w = length l ·∾ f "this" 5 ·∾ -- or, more simply ·∾ f s = length s + 1 ·∾ f "this" 5
This is supposed to be the identity function,
id
.Original:
\X = x
Corrected:
·∾ (\x -> x) (1,2) (1,2) ·∾ id'' x = x ·∾ id'' True True
When fixed, this function will return 1 from the value
(1,2)
.Original:
f (a b) = A
Corrected:
·∾ f (a,b) = a ·∾ f (1,2) 1
4.9.3 Match the function names to their types¶
Which of the following types is the type of show?
show a => a -> String
Show a -> a -> String
Show a => a -> String
This one; => is for type class contraints, and Show must be capatolized:·∾ :type show show :: Show a => a -> String
Which of the following types is the type of (==)?
a -> a -> Bool
Eq a => a -> a -> Bool
thisaone:·∾ :type (==) (==) :: Eq a => a -> a -> Bool
Eq a -> a -> a -> Bool
Eq a => A -> Bool
Which of the following types is the type of
fst
?
(a, b) -> a
this looks right:·∾ :type fst fst :: (a, b) -> a
b -> a
(a, b) -> b
Which of the following types is the type of
(+)
?(+) :: Num a -> a -> a -> Bool
(+) :: Num a => a -> a -> Bool
(+) :: num a => a -> a -> a
This one!·∾ :type (+) (+) :: Num a => a -> a -> a
(+) :: Num a => a -> a -> a
(+) :: a -> a -> a