Notes week of 1 Oct

if-then-else expression

We have used Boolean expressions to determine outcomes using the guard notation:

Here’s an alternative with nested if-then-else expressions:

case expression

We have used pattern matching for function clauses. We can also use the case keyword. The individual cases must be indented and aligned underneath the case keyword.

Instead of nested cases to process each argument, we could process patterns on the pair of arguments (xs,ys):

More on Maybe

A total function is a function that is defined (returns a value) for every element in its domain (argument type). A function that is not total is called partial.

take and drop are total functions because they produce a result when given any integer and any list:

ghci> take 5 [3,2]
[3,2]
ghci> take (-10) [3,2]
[]

Some examples of partial functions are head and tail (because they fail on empty lists), or div (due to division by zero):

ghci> head [3,5,7]
3
ghci> head []
*** Exception: Prelude.head: empty list
ghci> tail [3,5,7]
[5,7]
ghci> tail []
*** Exception: Prelude.tail: empty list
ghci> div 8 3
2
ghci> div 8 0
*** Exception: divide by zero

One way to transform a partial function into a total one is to have it return a Maybe value. Then we can use Nothing to represent the failure explicitly, or Just to represent a legitimate value. Here is a version of head that is total:

ghci> headMay [3,5,7]
Just 3
ghci> headMay []
Nothing

Having the Maybe there forces us to deal with the possibility of failure or missing data. We consider this to be a distinct advantage of typed functional programming. Suppose we want to use headMay to compose strings from parts of other strings:

ghci> headMay myName : "arl"
<interactive>:273:19-23: error:
    • Couldn't match type ‘Char’ with ‘Maybe Char’

The error here is telling us that we can’t directly use headMay myName as a character, because perhaps myName was the empty string and so retrieving its head element would fail.

There are a couple ways to deal with this. We can explicitly use a case statement to provide an expression to use in case of failure:

That pattern — returning Nothing if the result was Nothing, but applying some operation if the result was Just — is encapsulated into a handy function called fmap. It’s like the map on lists, but applies more generally to other data structures too, including Maybe.

The parenthesized (: "arl") is an operator section partially applying the list constructor. That means it’s a function which takes the character and joins it to the beginning of the string "arl". The function fmap can also be accessed symbolically with (<$>):

and then we don’t need the parentheses around headMay myName. We do still need the parens around (:"arl") because it’s an operator section.

Constructors with arguments

Define ‘selectors’ (getters)

Can use a record selector syntax like this:

Recursive data types

This is equivalent to how the built-in list is defined, but the type name and constructor names are different to avoid conflict:

Function composition

Function composition is denoted by a single dot (.), which we usually surround with spaces.

ghci> (half . square) 6
18
ghci> (square . half) 6
9

The functions are applied right-to-left, so we have:

  (half . square) 6 ↪ half (square 6) ↪ half 36 ↪ 18

  (square . half) 6 ↪ square (half 6) ↪ square 3 ↪ 9

You can compose any number of functions in this way. The (.) operator is right-associative.

ghci> (square . half . square . half) 10
144
ghci> (square . square . half . half) 12
81

Here is the derivation for that first example:

  (square . half . square . half) 10
↪ square (half (square (half 10)))
↪ square (half (square 5))
↪ square (half 25)
↪ square 12
↪ 144

Composing Maybes

Suppose we have versions of the above two functions that return Maybe values to represent some sort of failure. Maybe half only works on even numbers, and square refuses to multiply numbers that are “too large”:

These can’t directly compose, because one returns a Maybe Int and the next one expects just an Int:

ghci> (mhalf . msquare) 4
<interactive>:320:10-16: error:
    • Couldn't match type ‘Maybe Int’ with ‘Int’

However, there is a variant of the composition operator that composes functions returning Maybe values. To access it, we may need this line at the top of our program (or you can type it into GHCI).

Then we can do this:

ghci> (mhalf <=< msquare) 4
Just 8

It applies the square and then the half. Assuming both succeed, we get a Just result. But what if one fails? I could apply that to 5:

ghci> (mhalf <=< msquare) 5
Nothing

This squared 5 to get 25, but then mhalf found that 25 is not even, so it produced Nothing. The first operation can fail too. Here’s an example of that:

ghci> (mhalf <=< msquare) 128
Nothing

In this case, msquare refused to multiply and produced Nothing. So then the “pipeline” came to a halt — there was nothing available to pass to mhalf.

Either data type