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
  1. What is the type constructor, or name of this type?

    • Mood

  2. If the function requires a Mood value, what are the values you could possibly use?

    • Blah or Woot

  3. We’re writing a function called changeMood, and so far it has the type signature changeMood :: 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 as changeMood :: Mood -> Mood, instead.

  4. Fix any mistakes and complete the function:

    changeMood Mood = Woot
    changeMood    _ = Blah
    
    • This should instead be:

      changeMood Blah = Woot
      changeMood Woot = Blah
      
  5. 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 and maxBound from the Prelude.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 as Scientific.

You can use it like:

·∾ 3 / 4 :: Rational
3 % 4

Some fractional types have a instance of Real, which provides toRational. Using it, you can do conversions:

·∾ toRational 3.8
4278419646001971 % 1125899906842624

There is also a more general datatype called Ratio that allows a ratio of any two Integral types, rather than Integer 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 an Int.

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 or

  • not

  • 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

  1. not True && true should be not True && True

  2. not (x = 6) should be not (x == 6)

  3. (1 * 2) > 5

  4. [Merry] > [Happy] should be "Merry" > "Happy" , unless Merry and Happy are data constructors.

  5. [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.

  1. What do you think the type signature of length is?

    • length :: Num b => [a] -> b was my guess. It turns out that it’s length :: Foldable t => t a -> Int, instead.

  2. What are the results of the following expressions?

    1. length [1,2,3,4,5] –> 5

    2. length [(1,2),(2,3),(3,4)] –> 3

    3. length allAwesome –> 2

    4. length (concat allAwesome) –> 5

  3. Determine which expression of 6 / 3 and 6 / 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
    
  4. 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
    
  5. 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
    
  6. 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
    
  7. 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
    
  8. 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
    
  9. 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
    
  10. Fill in the definition of the function f :: (a, b) -> (c, d) -> ((b, d), (a, c)) using fst and snd:

    ·∾ :{
    ·∾ 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.

  1. 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
    
  2. 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
    
  3. 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

  1. Which of the following types is the type of show?

  1. show a => a -> String

  2. Show a -> a -> String

  3. Show a => a -> String This one; => is for type class contraints, and Show must be capatolized:

    ·∾ :type show
    show :: Show a => a -> String
    
  1. Which of the following types is the type of (==)?

  1. a -> a -> Bool

  2. Eq a => a -> a -> Bool thisaone:

    ·∾ :type (==)
    (==) :: Eq a => a -> a -> Bool
    
  3. Eq a -> a -> a -> Bool

  4. Eq a => A -> Bool

  1. Which of the following types is the type of fst?

  1. (a, b) -> a this looks right:

    ·∾ :type fst
    fst :: (a, b) -> a
    
  2. b -> a

  3. (a, b) -> b

  1. Which of the following types is the type of (+)?

    1. (+) :: Num a -> a -> a -> Bool

    2. (+) :: Num a => a -> a -> Bool

    3. (+) :: num a => a -> a -> a This one!

      ·∾ :type (+)
      (+) :: Num a => a -> a -> a
      
    4. (+) :: Num a => a -> a -> a

    5. (+) :: a -> a -> a