Chapter 7: More Functional Patterns

I think I’ll respond to the Sussman quote with an epigraph from chapter 1 of SICP.

The acts of the mind, wherein it exerts its power over simple ideas, are chiefly these three:

  1. Combining several simple ideas into one compound one, and thus all complex ideas are made.

  2. The second is bringing two ideas, whether simple or complex, together, and setting them by one another so as to take a view of them at once, without uniting them into one, by which it gets all its ideas of relations.

  3. The third is separating them from all other ideas that accompany them in their real existence: this is called abstraction, and thus all its general ideas are made.

—John Locke, An Essay Concerning Human Understanding (1690)

7.1 Make it func-y

This chapter explores common idioms enabled by first-class functions, pattern matching, and composition. Particularly, we will:

  • learn about the mechanics of pattern matching;

  • explore case expressions and guards – elegant control flow constructs that allow selection based on pattern matches and boolean expressions;

  • nest functions, experiment with lambda expression;

  • use combinators that change the order of function application, like (.) and $.

7.2 Arguments and parameters

Functions are defined by the fact that they can be applied to an argument and return a result.

A parameter is a name that acts as a placeholder for an input within a function definition. An argument is an input value that is applied to the function.

When you apply a function to an argument, the value of that argument is bound to a parameter name within the definition for that particular instance of the function, or that function call.

·∾ -- v parameter
·∾ id x = x

·∾ -- v argument
·∾ id 3 -- parameter x is bound to 3 for this particular function invocation.
3

Functions are applied to arguments which binds their parameters to values.

7.2.1 Setting parameters

As you add parameters infix type constructors for curried functions and their respective input type variables are added to the type signature.

·∾ -- Notice what happens to the type
·∾ -- signature as we introduce more
·∾ -- parameters.

·∾ myNum = 1 :: Integer

·∾ myVal f g = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> Integer

·∾ myVal f g h = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> p3 -> Integer

·∾ myVal f g h i = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> p3 -> p4 -> Integer

Saturating a parameter with an argument removes one -> a from the type signature.

·∾ -- And what happens as we apply
·∾ -- arguments to those parameters.

·∾ myVal f g h i = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> p3 -> p4 -> Integer

·∾ :type myVal 1
myVal :: p1 -> p2 -> p3 -> Integer

·∾ :type myVal 1 2
myVal :: p1 -> p2 -> Integer

·∾ :type myVal 1 2 3
myVal :: p1 -> Integer

·∾ :type myVal 1 2 3 4
myVal :: Integer

7.2.2 Binding variables to values

Haskell is lexically scoped. This means that resolving the value for a named entity depends on the names location in source code. The lexically innermost binding for a variable of a particular name always takes precedence. If an inner scope defines a name that already exists in the outer scope, it shadows that name, so the outer definition is no longer visible within that inner scope.

In the example here, the parameter x is shadowed by the definition for x within the let expression, essentially blocking the visibility of the parameter:

·∾ :{
 ⋮ bindExp :: Integer -> String
 ⋮ bindExp x = let x = 10; y = 5 in
 ⋮               "x: " ++ show x ++
 ⋮               ", "  ++
 ⋮               "y: " ++ show y
 ⋮ :}
·∾ bindExp 8
"x: 10, y: 5"

In a repl session, each line shadows the next. The mechanics of this are conceptually similar to nested lambda expressions.

·∾ x = 5
·∾ y = x + 5
·∾ y
10

This analogy begins to break down when you consider top-level declarations, like imports, though.

Also note that multi-line expressions are treated as one logical “line”.

·∾ -- multi-line expressions are don't shadow names until they're ended
·∾ :{
 ⋮ x = 3
 ⋮ x = 4
 ⋮ :}
<interactive>:93:1: error:
    Multiple declarations of ‘x’
    Declared at: <interactive>:91:1
                 <interactive>:93:1

7.3 Anonymous functions

Anonymous functions are function literals, or functions without a name.

Here is a named function:

triple :: Integer -> Integer
triple x = x * 3

…and here is the same function but with anonymous function syntax:

(\x -> x * 3) :: Integer -> Integer

7.3.1 Grab Bag

  1. Which (two or more) of the following are equivalent?

    1. mTh x y z = x * y * z

    2. mTh x y = \z -> x * y * z

    3. mTh x = \y -> \z -> x * y * z

    4. mTh = \x -> \y -> \z -> x * y * z

    All of these are equivalent. Here’s proof:

    ·∾ mTh x y z = x*y*z
    ·∾ :type mTh
    mTh :: Num a => a -> a -> a -> a
    
    ·∾ mTh x y = \z -> x*y*z
    ·∾ :type mTh
    mTh :: Num a => a -> a -> a -> a
    
    ·∾ mTh x = \y -> \z -> x*y*z
    ·∾ :type mTh
    mTh :: Num a => a -> a -> a -> a
    
    ·∾ mTh = \x -> \y -> \z -> x*y*z
    ·∾ :type mTh
    mTh :: Num a => a -> a -> a -> a
    
  2. The type of mTh (above) is Num a => a -> a -> a -> a. Which is the type of mTh 3?

    1. Integer -> Integer -> Integer

    2. Num a => a -> a -> a -> a

    3. Num a => a -> a

    4. Num a => a -> a -> a

      ·∾ :type mTh 3
      mTh 3 :: Num a => a -> a -> a
      
  3. Next we’ll practice writing anonymous lambda syntax.

    1. Rewrite the f function in the where clause.

      The named function:

      addOneIfOdd n = case odd n of
        True -> f n
        False -> n
        where f n = n + 1
      

      Becomes:

      addOneIfOdd n = case odd n of
        True -> (\n -> n + 1) n
        False -> n
      

      Proof it works:

      ·∾ addOneIfOdd 8
      8
      ·∾ addOneIfOdd 9
      10
      
    2. Rewrite the following to use anonymous lambda syntax:

      addFive x y = (if x > y then y else x) + 5
      

      Rewritten:

      (\x y -> (if x > y then y else x) + 5)
      

      Proof it works:

      ·∾ addFive x y = (if x > y then y else x) + 5
      ·∾ addFive 4 10
      9
      ·∾ addFive 20 10
      15
      
      ·∾ (\x y -> (if x > y then y else x) + 5) 4 10
      9
      ·∾ (\x y -> (if x > y then y else x) + 5) 20 10
      15
      
    3. Rewrite the following so that it doesn’t use anonymous lambda syntax:

      mflip f = \x -> y -> f y x
      

      Rewritten:

      mflip f x y = f y x
      

      Proof it works:

      ·∾ mflip f = \x -> \y -> f y x
      ·∾ mflip (^) 2 5
      25
      ·∾ (^) 2 5
      32
      
      ·∾ mflip f x y = f y x
      ·∾ mflip (^) 2 5
      25
      ·∾ (^) 2 5
      32
      

7.3.2 The utility of lambda syntax

You’ll most often use lambdas when you’re passing a function as an argument, and that’s the only place in your program where you’ll need it. If you aren’t going to call the function again, why give it a name?

7.4 Patten matching

Pattern matching allows you to do three things:

  • compare data constructors against an input (matching);

  • access arguments or fields of a data constructor (destructuring);

  • and bind names to successful matches of data constructors or their destructured arguments (name binding).

You can think of patterns as coming in two varieties; those composed only of values, and those that introduce names.

Value only patterns

Any data constructor can be matched, like True, or 1, or even compound structures like (1,2), Just 3, and [('c',2),('d',4)].

This function matches a pattern of only values, but doesn’t do any destructuring or name binding. Even literal values are patterns. It returns True if given 1, and throws an exception for anything else:

isOne 1 = True

In ghci:

·∾ isOne 1
False
·∾ isOne 8
*** Exception: <interactive>:185:1-14: Non-exhaustive patterns in function isOne

Destructuring value only patterns

Here is a function that works on a more complicated structure. This pattern uses destructuring to access sub-elements, but does not bind names. It will return False if you give it this very particular tuple literal:

contrived :: ([a], Char, (Int, Float), String, Bool) -> Bool
contrived    ([],  'b',  (1,   2.0),   "hi",   True) = False

In ghci:

·∾ contrived ([], 'b', (1, 2.0), "hi", True)
False

If you give it a different tuple, you’ll get an error, since we haven’t defined a catch-all pattern:

·∾ contrived ([], 'F', (8, 9.8), "bye", False)
*** Exception: <interactive>:165:1-49: Non-exhaustive patterns in function contrived

Patterns that introduce names

Patterns look a lot like data constructors, but in place of data constructor parameters you can supply a name, instead. If a value (or argument) inhabits that parameter, it gets bound to the name you’ve supplied.

Name binding and destructuring

Here is an example pattern that assigns the third element of a triple to a and discards the rest:

·∾ (_,_,a) = (1,2,3)
·∾ a
3

The _ you see here is a wildcard – a special name that always matches and throws away the result.

Mechanics of pattern matching

The matching process itself occurs “top-down, left-to-right.”

Pattern matching can either fail, succeed or diverge. A successful match binds the formal parameters in the pattern. Divergence occurs when a value needed by the pattern contains an error (⊥).

Failure of a pattern anywhere in one equation results in failure of the whole equation, and the next equation is then tried. If all equations fail, the value of the function application is ⊥, and results in a run-time error.

Patterns in any one equation are not allowed to have more than one occurrence of the same formal parameter (a property called linearity). Except for the wildcard, _, which you can use more than once.

Lazy patterns, refutable vs irrefutable patterns

There is one other kind of pattern allowed in Haskell. It is called a lazy pattern, and has the form ~pat.

Lazy patterns are irrefutable: matching a value v against ~pat always succeeds, regardless of pat. If an identifier in pat is later “used” on the right-hand-side, and it didn’t match a value, it will return bottom _|_.

Refutable patterns are distinct from irrefutable ones in that they can fail to match.

Lazy patterns are useful in contexts where infinite data structures are being defined recursively.

Here’s an equation that defines the Fibonacci sequence:

fib@(1:tfib)    = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]

This kind of equation is called a pattern binding because it is a top-level equation in which the entire left-hand side is a pattern.

The @ symbol here indicates we are using an as-pattern, where everything in the parenthesis are captured by the name fib.

Where else can you use patterns?

Patterns can appear in a lot of places. To be specific, patterns can be used with

  • lambda abstractions;

  • function definitions;

  • pattern bindings;

  • list comprehensions;

  • do expressions;

  • and case expressions.

Ultimately, the first five of these translate into case expressions. A good general rule of thumb is “if you can put a case expression inside of it, it probably supports pattern matching itself.”

7.4.1 Handling all the cases

Here’s a more pragmatic concern – if you write your patterns in the wrong order, your function will fail.

In the function isItTwo the wildcard is always matched, and it wont ever return True for 2:

isItTwo :: Integer -> Bool
isItTwo _ = False
isItTwo 2 = True

Here’s what happens if we load this into ghci and try:

·∾ :load is.hs
[1 of 1] Compiling Main             ( is.hs, interpreted )
is.hs:3:1: warning: [-Woverlapping-patterns]
    Pattern match is redundant
    In an equation for ‘isItTwo’: isItTwo 2 = ...
  |
3 | isItTwo 2 = True
  | ^^^^^^^^^^^^^^^^
Ok, one module loaded.
·∾ isItTwo 8
False
·∾ isItTwo 2
False

This is called an overlapping pattern. To avoid this situation, try to order you patterns from most to least specific, particularly when using the wildcard.

Another issue is that of non-exhaustive patterns. This is when you don’t have patterns defined to deal with every case. (A partial function.) You patterns will then fail to match, and return bottom, which will throw an exception, that if unhandled will make your program fail.

Turn on -Wall and GHC will warn you about overlapping and non-exhaustive patterns. (Along with a bunch of other things!)

7.4.4 Exercises: Variety Pack

  1. Given the following declarations

    k (x, y) = x
    k1 = k ((4-1), 10)
    k2 = k ("three", (1 + 2))
    k3 = k (3, True)
    
    1. What is the type of k?

      I’m guessing that it should be :: (x,y) -> x; The tuple constructor matches term-level pretty well. Let’s see if I’m right:

      ·∾ :type k
      k :: (a, b) -> a
      
    2. What is the type of k2? Is it the same type as k1 or k3?

      k2 :: String ; k1 :: Num a => a; k3 :: Num a => a; are my guesses. This would mean that k2 has a different type than k1 and k3. Let’s see:

      ·∾ :type k2
      k2 :: [Char]
      
    3. Of k1, k2, k3, which will return the number 3 as the result?

      Of course k1 and k3 will, they both have 3 as their first element:

      ·∾ k3
      3
      ·∾ k1
      3
      
  2. Fill in the definition of the following function

    f :: (a, b, c) -> (d, e, f) -> ((a, d), (c, f))
    f = undefined
    

    Sure. f (a,_,c) (d,_,f) = ((a,d),(c,f)). Is that right? Let’s test it:

    ·∾ f :: (a, b, c) -> (d, e, f) -> ((a, d), (c, f)); f = undefined
    ·∾ f (a,_,c) (d,_,f) = ((a,d),(d,f))
    
    ·∾ :type f
    f :: (a1, b1, c) -> (a2, b2, b3) -> ((a1, a2), (a2, b3))
    
    ·∾ f (1,2,3) (4,5,6)
    ((1,4),(4,6))
    

    It works!

7.5 Case expressions

Case expressions are control flow constructs that perform selection based on pattern matches.

Case expressions look like this:

value :: Value -> Integer
value card = case card of
  Two    -> 2
  Three  -> 3
  Four   -> 4
  Five   -> 5
  Six    -> 6
  Seven  -> 7
  Eight  -> 8
  Nine   -> 9
  Ten    -> 10
  Jack   -> 10
  Queen  -> 10
  King   -> 10
  Ace    -> 1

Each pattern -> expression pair is known as a match, but sometimes I’ll call these arms, instead.

7.5.1 Exercises: Case Practice

First, rewrite if-then-else expressions into case expressions.

  1. The following should return x when x is greater than y:

    functionC x y = if x > y then x else y
    

    Rewritten:

    functionC x y =
      case x > y of
        True  -> x
        False -> y
    

    Proof it works:

    ·∾ functionC x y = case x > y of { True -> x; False -> y }
    ·∾ functionC 3 8
    8
    ·∾ functionC 8 3
    8
    ·∾ functionC 1 9
    9
    ·∾ functionC 9 1
    9
    ·∾ functionC 10 1
    10
    
  2. The following will add 2 to even numbers and otherwise simply return the input value:

    ifEvenAdd2 n = if even n then (n+2) else n
    

    Rewritten:

    ifEvenAdd2 n = case even n of { True -> n+2; False -> n }
    

    Proof it works:

    ·∾ ifEvenAdd2 n = if even n then n+2 else n
    ·∾ ifEvenAdd2 2
    4
    ·∾ ifEvenAdd2 8
    10
    ·∾ ifEvenAdd2 9
    9
    
    ·∾ ifEvenAdd2 n = case even n of { True -> n+2; False -> n }
    ·∾ ifEvenAdd2 2
    4
    ·∾ ifEvenAdd2 8
    10
    ·∾ ifEvenAdd2 9
    9
    

    The next exercise doesn’t have all the cases covered. See if you can fix it.

  3. The following compares a value, x, to zero and returns an indicator for whether x is a positive number or negative number. What if x is 0?

    You may need to play with the compare function a bit to find what to do:

    nums :: Num x => x -> Ordering
    nums x =
      case compare x 0 of
        LT -> -1
        GT ->  1
    

    Rewritten:

    nums :: Num x => x -> Ordering
    nums x =
      case compare x 0 of
        LT -> -1
        GT ->  1
        EQ ->  0
    

    Proof it works:

    ·∾ nums x = case compare x 0 of { LT -> -1; GT -> 1; EQ -> 0; }
    ·∾ nums 0
    0
    ·∾ nums 8
    1
    ·∾ nums (-9000)
    -1
    

Did you know case expressions can contain guards? This is a bad example, but it shows the correct syntax. Note that we are using -> rather than = to terminate the guard.

justPositiveOver12 mx =
  case mx of
    Just x | x > 12  -> Just x
           | x <= 12 -> Nothing
    _              -> Nothing

BNF of case expression taken from the 2010 Haskell Report, 3.13:

lexp   -> case exp of { alts }
alts   -> alt₁; ... ; altₙ           (n >= 1)
alt    -> pat -> exp [where decls]
        | pat gdpat [where decls]
        |                            (empty alternative)
gdpat  -> guards -> exp [gdpat]
guards -> | guard₁, ..., guardₙ      (n >= 1)
guard  -> pat <- infixexp            (pattern guard)
        | let decls                  (local declaration)
        | infixexp

Apparently, you can have let expressions within guards. Neat. If you don’t know BNF, I can recommend this guide to learn it: https://www.ics.uci.edu/~pattis/ICS-33/lectures/ebnf.pdf.

7.6 Higher-order functions

Higher-order functions are functions that accept functions as arguments.

--      input function
--    that takes two args
--      vvvvvvvvvvvvv
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x

7.6.1 Exercises: Artful Dodgy

Given the following definitions tell us what value results from further applications. (For bonus points, fill in the type signatures of these functions.):

dodgy x y = x + y * 10
oneIsOne = dodgy 1
oneIsTwo = (flip dodgy) 2

Navigate to exercises/7.6.1_-_artful_dodgy.rst.d/dodgy/ and run stack test for proof.

  1. dodgy 1 0 –> 1

  2. dodgy 1 1 –> 11

  3. dodgy 2 2 –> 22

  4. dodgy 1 2 –> 21

  5. dodgy 2 1 –> 13

  6. oneIsOne 1 –> 11

  7. oneIsOne 2 –> 21

  8. oneIsTwo 1 –> 21

  9. oneIsTwo 2 –> 22

  10. oneIsOne 3 –> 31

  11. oneIsTwo 3 –> 23

7.7 Guards

7.7.2 Writing guard blocks

Here’s what a function with a guard block looks like:

myAbs x
  | x < 0     = (-x)
  | otherwise = x

The condition between | and = is a boolean expression. If it returns True, then the expression on the right of the = is evaluated.

Guards always evaluate sequentially, so your guards should be ordered from the case that is most restrictive to the case that is least restrictive.

Surprise! Matches in case expressions can contain guards!

absoluteJust :: Maybe Int -> Maybe Int
absoluteJust n = case n of
  Nothing -> Nothing
  Just n
    | n < 0     -> Just (-n)
    | otherwise -> Just n

Note that otherwise is just an another name for True, provided by Prelude to make guards more pleasant to read.

7.7.3 Guard Duty

To check my answers for yourself, navigate to the guard-duty directory and run stack test.

  1. It is probably clear to you why you wouldn’t put an otherwise in your top-most guard, but try it with avgGrade anyway and see what happens. It’ll be more clear if you rewrite it as an otherwise match: | otherwise = 'F'.

    Ok, my altered version, named avgGradeUnconditional, looks like this:

    avgGradeUnconditional x
      | otherwise = 'F'
      | y >= 0.9 = 'A'
      | y >= 0.8 = 'B'
      | y >= 0.7 = 'C'
      | y >= 0.59 = 'D'
      | y < 0.59 = 'F'
      where y = x / 100
    

    What happens now if you pass a 90 as an argument? 75? 60?

    Since the first case is always evaluated, you will get something like this:

    ·∾ avgGradeUnconditional 90
    'F'
    ·∾ avgGradeUnconditional 75
    'F'
    ·∾ avgGradeUnconditional 60
    'F'
    
  2. What happens if you take avgGrade as it is written and reorder the guards? Does it still typecheck and work the same? Try moving | y >= 0.7 = 'C'.

    My altered version, named avgGradeReordered looks like this:

    avgGradeReordered x
      | y >= 0.7 = 'C'
      | y >= 0.9 = 'A'
      | y >= 0.8 = 'B'
      | y >= 0.59 = 'D'
      | y < 0.59 = 'F'
      where y = x / 100
    

    Pass it the argument 90, which should be an 'A'. Does it return an 'A'?

    No, instead I get this:

    ·∾ avgGradeReordered 90
    'C'
    

    If I want to make the cuases independent of order, I can add a minimum bound to the range we’re checking, like this:

    avgGradeOrderIndependent x
      | y >= 0.7  && y < 0.8 = 'C'
      | y >= 0.9 = 'A'
      | y >= 0.8 && y < 0.9 = 'B'
      | y >= 0.59 && y < 0.7 = 'D'
      | y < 0.59 = 'F'
      where y = x / 100
    
  3. The following function returns

    pal xs
      | xs == reverse xs = True
      | otherwise = False
    
    1. xs written backwards when it’s True

    2. True when xs is a palindrome

    3. False when xs is a palindrome

    4. False when xs is reversed

  4. What types of arguments can pal take?

    Let me think about it. Pal uses (==) and reverse – what constraints do those functions add?

    ·∾ :type (==)
    (==) :: Eq a => a -> a -> Bool
    ·∾ :type reverse
    reverse :: [a] -> [a]
    

    …and it returns a Bool. So my guess is pal :: (Eq a) => [a] -> Bool, which means that it can take any Eq constrained list as an input.

  5. What is the type of the function pal?

    I reasoned about this in the last answer, but let me check:

    ·∾ :type pal
    pal :: Eq a => [a] -> Bool
    
  6. The following function returns

    numbers x | x < 0 = -1
              | x == 0 = 0
              | x > 0 = 1
    
    1. the value of its argument plus or minus 1

    2. the negation of its argument

    3. an indication of whether its argument is a positive or negative number or zero (This is basically signum.)

    4. binary machine language

  7. What types of arguments can numbers take?

    Since (==) adds a constraint for Eq and (>) adds Ord and it works on numbers, the input type should be (Num a, Ord a) => a, I think.

  8. What is the type of the function numbers?

    My guess is numbers :: (Ord a, Num a) => a -> a (the (>) operator adds Ord). Let me check:

    ·∾ :type numbers
    numbers :: (Ord a, Num a, Num p) => a -> p
    

    Ok, so the result type is also Num constrained, but doesn’t require an Ord instance.

    Let me think about that. … I’m not sure that makes sense. But if I do:

    ·∾ :{
     ⋮ numbers :: (Ord a, Num a) => a -> a
     ⋮ numbers x | x < 0 = -1
     ⋮           | x == 0 = 0
     ⋮           | x > 0 = 1
     ⋮ :}
    ·∾ :type numbers
    numbers :: (Ord a, Num a) => a -> a
    

    It typechecks! This works since we’re making the result type less polymorphic.

7.8 Function composition

The compose operator is a type of higher-order function that allows us to combine function such that the result of applying one function gets passed to the next function as an argument.

Type signature and definition:

--      left         right
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = f (g x)
infixr 9 .

(Ordinary function application has a precedence of 10 out of 10.)

Usage:

·∾ negate . sum $ [1..4]
-10

Parenthesis or $ are needed to separate the group of composed functions and the argument they’ll operate on.

7.9 Pointfree style

Pointfree refers to a style of composing functions without specifying their arguments. The “point” in “pointfree” refers to the arguments, not the composition operator.

The idea is to put an emphasis on series of transformations by eliding temporary name bindings. In combination with compose, this feels reminiscent of unix shell pipelines (but in reverse). Use this only where it makes sense. If keeping track of parameter names is getting confusing, maybe that’s a good candidate for pointfree.

A simple example:

-- pointful
f z xs = foldr (+) z xs

-- pointfree
f = foldr (+)

g = negate . sum

Another one:

·∾ f = length . filter (== 'a')
·∾ f "abracadabra"
5

Whenever I write pointfree code, it feels satisfying. But when I read other people pointfree code, I hate it.

7.11 Chapter Exercises

7.11.1 Multiple choice

  1. A polymorphic function

    1. changes things into sheep when invoked

    2. has multiple arguments

    3. has a concrete type

    4. may resolve to values of different types, depending on inputs

  2. Two functions named f and g have types Char -> String and String -> [String] respectively. The composed function g . f has the type

    1. Char -> String

    2. Char -> [String]

      Proof:

      ·∾ f :: Char -> String; f = undefined
      ·∾ g :: String -> [String]; g = undefined
      ·∾ :type g . f
      g . f :: Char -> [String]
      
    3. [[String]]

    4. Char -> String -> [String]

  3. A function f has the type Ord a => a -> a -> Bool and we apply it to one numeric value. What is the type now?

    1. Ord a => a -> Bool

      Proof:

      ·∾ f :: Ord a => a -> a -> Bool; f = undefined
      ·∾ :type f 1
      f 1 :: (Ord a, Num a) => a -> Bool
      
    2. Num -> Num -> Bool

    3. Ord a => a -> a -> Integer

    4. (Ord a, Num a) => a -> Bool

  4. A function with the type (a -> b) -> c

    1. requires values of three different types

    2. is a higher-order function

    3. must take a tuple as its first argument

    4. has its parameters in alphabetical order

  5. Given the following definition of f, what is the type of f True?

    f :: a -> a
    f x = x
    
    1. f True :: Bool

      Proof:

      ·∾ :{
       ⋮ f :: a -> a
       ⋮ f x = x
       ⋮ :}
      ·∾ :type f True
      f True :: Bool
      
    2. f True :: String

    3. f True :: Bool -> Bool

    4. f True :: a

7.11.2 Let’s write code

  1. The following function returns the tens digit of an integral argument.

    tensDigit :: Integral a => a -> a
    tensDigit x = d
      where xLast = x `div` 10
            d     = xLast `mod` 10
    
    1. First, rewrite it using divMod.

      Here you go!

      tensDigit' x = let (y,_) = x `divMod` 10 in y `mod` 10
      
    2. Does the divMod version have the same type as the original version?

      Yes, here’s the proof:

      ·∾ :type tensDigit
      tensDigit :: Integral a => a -> a
      
      ·∾ :type tensDigit'
      tensDigit' :: Integral a => a -> a
      
    3. Next, let’s change it so that we’re getting the hundreds digit instead. You could start it like this (though that may not be the only possibility):

      hunsD x = d2
        where d = undefined
        ...
      

      Ok, here is my attempt

      hunsD x = (x `div` 100) `mod` 10
      
  2. Implement the function of the type a -> a -> Bool -> a once each using a case expression and once with a guard.

    {-# ANN foldBool ("HLint: ignore Use if" :: String) #-}
    foldBool :: a -> a -> Bool -> a
    foldBool x y b = case b of { False -> x; True -> y }
    
  3. Fill in the definition

    g :: (a -> b) -> (a, c) -> (b, c)
    g = undefined
    

    Alright, here we go.

    g :: (a -> b) -> (a, c) -> (b, c)
    g aToB (a, c) = (aToB a, c)
    
  4. Write the following code into a source file. Then load it and run it in GHCi to make sure you understand why the evaluation results in the answers you see.

    -- arith4.hs
    module Arith4 where
    -- id :: a -> a
    -- id x = x
    roundTrip :: (Show a, Read a) => a -> a
    roundTrip a = read (show a)
    main = do
      print (roundTrip 4)
      print (id 4)
    
  5. Next, write a pointfree version of roundTrip. (n.b., This refers to the function definition, not to its application in main.)

    roundTrip' = read . show
    
  6. I’m pretty sleep deprived and don’t understand this question.