Login

Please fill in your details to login.





s5cs50 functional programming

Yet another programming language, but it's OK because we'll be using Python as well as Haskell.
image

Yet another programming language (and only AQA), but it's OK because we'll be using Python as well as Haskell.

We are learning ...

About the meaning of the term function
How to implement functions in a functional programming language
Applications of functional programming

So that we can ...

State the format of a function as ⨍ : A ↦ B
- A ↦ B is the function type
- A is called the argument type or domain
- B is called the result type or co-domain
- A and B are always subsets of objects
Functions map one set of values (domain) to another set of values (drawn from codomainI have no idea what this means)
Describe a function as a first class objectI have no idea what this means in functional programming
State that a function application means a function applied to it's arguments
State what is meant by partial function application for one, two and three argument functions
State what is meant by composition of functions
Show experience of constructing simple programs in a functional programming language
- Map
- Filter
- Reduce / fold
Higher order functions
Be familiar with representing a list as a concatenation of a head and a tail
- The head is an element of a list
- The tail is a list
- The list can be empty
- Operations (return head, return tail, test for empty list, return length of list, construct empty list, prepend an item to a list, append an item to a list)
- Have experience writing programs for list operations in functional programming language
Application of functional programming to big data / distributed code

Up until now, we have mainly been learning procedural and object-oriented programming. We dabbled a little with a declarative programming language (SQL) but now, we learn the fundamentals of a fully blown declarative language!

image

Activity 1
Functions

We have met functions before. A function is a rule which relates an input to an output. It maps values in a domain to values in a codomain. The values in the codomain to which our function maps are in the range of the function. We met this definition before when we learnt about vectors.

image

time limit
Task 1.1 Maths is fun!

Visit the Maths is Fun website and read through the following pages ...


Make notes and answer questions 1 to 10 at the bottom of each page.

OUTCOME : Notes on functions, domain, range and codomain when applied to functions.

Checkpoint

image

Function type

The domain and the codomain are also related to the function type. The type of a function states the data type of the argument the function requires (the value in the brackets) and the data type it will return. All elements in the domain must be of the same type as must all elements in the codomain.

The type of function f is written as ⨍ : A ⟼ B where A is the type of the domain and B is the type of the codomain.

time limit
Task 1.2 Function types
Describe a function, ⨍, with the function type stated.

1
⨍ : Integer ⟼ Integer
2
⨍ : Integer ⟼ Real
3
⨍ : Char ⟼ Integer
4
⨍ : String ⟼ Integer
5
⨍ : Real ⟼ Bool

OUTCOME : Examples of function types stated.

Checkpoint

image

Activity 2
Functional programming

Before you start, forget everything you already know about programming (eek!) You don't think imperatively and you don't code imperatively. Everything in functional programming is a function, just like everything in object oriented programming is an object (although the methods you code are imperative!) You have to think of the world as functions. Clearly, applications of functional programming lean towards mathematical problems which require lots of calculations.

In functional programming ...
functions are pure - they only modify their parameters and nothing else which prevents side effects;
functions with no arguments are called definitions (we'd know them as assignment);
useful functions return a value;
functions are predictable - they always produce the same outputs given the same inputs;
there are no variables!
there are no loops so we have to use recursion 😄

image
OK, OK, keep calm ... here comes the maths!

Function application

In mathematics, function applicationI have no idea what this means is the act of applying a function to argument(s), drawn from the domain, A, so as to obtain the corresponding value from its range within the codomain, B.

To define a function to
double
a value,
x
, in Python we could type ...

def double(x): return x*2


...whereas in a functional programming language, like Haskell, we write it like this ...

double x = x * 2


In imperative languages, function application is denoted using parentheses whereas in functional languages it is denoted using a space. In each case,
x
, is an argument to the function. The function is called
double
; its application is
double(x)
in Python and
double x
in Haskell.

Task 2.1 Function application

In your notebooks : Explain the meaning of function application using suitable examples which are different than the examples I have used.

OUTCOME : Your own examples of function and partial function application

Checkpoint

image

Practical functional programming

To learn some functional programming, we're going to use a very widely implemented platform called Haskell.

image
Click to visit the Haskell Website

You may wish to spend a little time on the tutorial that you can access by typing help in the 'Try It' section next to the lambda (λ) symbol (derived from it's history in Lambda calculus) ... see how far you can get before you get confused! To progress further, you can either install Haskell yourself on your own computer or ask your teacher to install it on your lab computers at school.

On Windows, Haskell comes with an interactive tool called WinGHCi or the Glasgow Haskell Compiler (Interactive) into which you can issue Haskell commands interactively or load and execute scripts.

image
The Glasgow Haskell Compiler (Interactive) running on Windows

time limit
Task 2.2 Keep it simple, Haskell!

To be honest, learning a whole new programming paradigm might be a bit too much to ask at this stage. However, I would like you to have a go the first part of the first chapter of the book Learn You a Haskell for Great Good up to but not including 'An intro to lists' - we'll cover that part later.

image

Use WinGHCi to try out the commands that you are taught. Don't worry about evidencing what you have done but do answer the following questions in your notebooks ...

1
How do you carry out simple mathematical operations using Haskell?
2
What are the symbols for Boolean
AND
and Boolean
OR
in Haskell?
3
What are the symbols for equality and inequality in Haskell?
4
What does the Haskell
succ
function do?
5
What do the Haskell
min
and
max
functions do and how many arguments do they take?
6
How do you create a simple function in Haskell? Can you give an example?
7
How do you use selection in functions in Haskell? Why is the
else
part mandatory?

http://learnyouahaskell.com/starting-out
I bought a copy, so should you!

OUTCOME : Simple Haskell examples to whet your appetite.

Checkpoint


First class objects / values

A first class object / value is any object / value that can ...

appear in an 'expression';
be assigned to or named by a variable / identifier
be stored in a list or a dictionary
be passed as arguments to a function
be returned from a function

... which pretty much covers everything in most modern programming languages. As you can imagine, by these definitions, all simple scalarI have no idea what this means data types such as integer, float, bool and char are first class objects and so are data structures which contain them. In Python, objects also act as first class as we have seen before when we have included objects assigned to variables, stored in lists, passed as parameters to functions or returned from functions.

Task 2.3 First class objects

Your challenge is to come up with one example of either a scalar data type (EASY), a data structure (HARDER) or a simple object (HARDEST) and demonstrate it behaving like a first class object in Python. Present your findings using a suitable combination of screenshots and written explanation.

OUTCOME : A first class object behaving like a first class object.

Checkpoint

Lambda functions

image
Getting Python functions to behave like first class objects is a little more tricky because we need functions to act as values. In Python, we can use anonymous lambda functions (don't be scared). A lambda function is a 'throw-away' function that we define and use in context - it does not exist, nor can it be referenced, outside this context. We create lambda functions using the
lambda
keyword and we can assign them to variables, store them in lists, pass them to functions and return them from functions. Truly 'first class'!

image

Try using Lambda to create an anonymous function.

>>> lambda x : x*2
<function <lambda> at 0x02FFCC00>


This is clearly a function (because it says so) but we have no way of 'holding' onto the lambda function - it is anonymous and has no identifier! So, let's give it one.

>>> double = lambda x : x*2
>>> double(4)
8


We've created a function called double and we've assigned it straight to a variable (first class) so it acts like a value! Nooo way!! We could have done this ...

>>> def double(x):return x*2
>>> double(4)
8


...so why bother with lambda? Mainly because it's easier and clearer but mainly because it is the only sensible way (not the only way) of including a function inside another function. Seriously, you'll see the benefit of using lambda later on. We can even pass a lambda function as a parameter to another function ...

>>> def fourtimes(function,parameter):
        return function(parameter) * 2
>>> fourtimes(lambda x: x*2, 4)
16


image

Here, the function
fourtimes
is behaving as a higher order function - one which takes another function as a parameter. The most common (built in) higher order functions are
map
,
filter
and
reduce
/
fold
. We'll meet these later.

What about this?

>>> def exponent(n): return lambda m : m**n
>>> exponent(3)
<function exponent.<locals>.<lambda> at 0x02FFCD20>


So, the function exponent returns an actual function - this is clear (and totally first class). The function
exponent
is also behaving in a higher order fashion as it is returning a function. We certainly can't do this with normal functions (in Python at least).

If you think about it, if
exponent
returns a function rather than a value then the function has not been fully applied This is known, unsurprisingly, as partial function application - the function exponent has been partially applied and is waiting for it's missing argument,
m
.

We can complete the application of the function by using it to create other functions, like this ...

>>> cube = exponent(3)
>>> cube(2)
8
>>> cube(3)
27


Wow! Like, wow! The function
cube
, completed the application of the function exponent by providing the final argument that it needs. Nice!

Finally, let's create a list of anonymous functions and access them like value 🤯 to complete our proof of first classness!

>>> myfunctions = [lambda n:n*1,lambda n:n*2,lambda n:n*4]
>>> myfunctions[0](2)
2
>>> myfunctions[1](2)
4
>>> myfunctions[2](2)
8


Here, we've seen lambda functions behaving in a truly first class fashion. Nice.

image
So is mine!

Task 2.4 Lambda functions

Anonymous lambda functions are somewhat of a mystery to most beginning Python programmers, but not you! Try the following exercises, providing evidence of what you have done through a suitable combination of screenshots and written explanation.

image

Create a lambda function,
half
, which returns half of it's argument
Create a lambda function,
reciprocal
, which returns the reciprocal of it's argument
Create a new function,
polynomial
, which returns the value of 2x2+3x-4
Using the existing function,
exponent
, create a new lambda function,
squareroot
, which returns the square root of it's argument;
Create a new function,
incrementer
, with one argument,
n
, which returns a lambda function taking another argument,
m
, which returns
m + n
. Use this function to create two more lambda functions,
successor
and
predecessor
.

Finished? Have a quick read through 'Yet Another Lambda Tutorial' - are you a convert?

OUTCOME : Some groovy lambda functions.

Checkpoint

image

Luckily, if we use Haskell, we don't generally have to use lambda functions for simple operations like this because in Haskell, the way that functions are defines is quite lambda like anyway. The safest way to conduct the next set of exercises is to use a script rather than the interactive console. Often Haskell requires you to be more explicit about the function types that you are defining and you can't define function types in Haskell at the interactive prompt 😔

image

Using your favourite text editor, create the a script called activity2.hs and save it in a suitable place. Make sure that the file extension is correct and close down your text editor. Now open up WinGHCi (if you haven't done already) and choose 'File > Load' and load in the script you have created. These are the two toolbar buttons you'll need next ...

image

Once the file is loaded, hit the 'Run Text Editor' button and Notepad should appear, as if by magic. Use WinGHCi and the default text editor to carry out the following tasks. Edit the script in the text editor, click save and then click the 'Reload Script' button before executing the commands given at the interactive prompt. Here we go ...

We can define the same double function in Haskell like this ...

In the text editor ... (save)

-- Define double function
double :: Integer -> Integer
double x = x * 2

In WinGHCi ... (reload file and execute)

*Main> double 4
8


In the text editor, you declare the function type explicitly so that Haskell knows what data type to expect and what data type it's meant to return. We can then use this double function in another function ...

In the text editor ... (save)

-- Define double function
double :: Integer -> Integer
double x = x*2

-- Define quadruple function
quadruple :: Integer -> Integer
quadruple x = double (double x)

In WinGHCi ... (reload file and execute)

*Main> quadruple 4
8


This is a very simple example of a very common pattern you see in Haskell where basic functions are designed and combined to create more complex functions. Partial function application in Haskell is also straightforward. The exponent examples we saw before is easy but we need to make sure we declare a specific function type to allow us to perform all the operations we did with Python before.

In the text editor ... (save)

-- Define double function
double :: Integer -> Integer
double x = x*2

-- Define quadruple function
quadruple :: Integer -> Integer
quadruple x = double (double x)

-- Define exponent' function
exponent' :: Float -> Float -> Float
exponent' x y = x ** y

In WinGHCi ... (reload file and execute)

*Main> exponent' 2 3
9.0
*Main> cube = exponent' 3
*Main> cube 2
8.0
*Main> cube 3
27.0


Notice the the prime (') character after the
exponent'
function name. The prime has no special meaning in this context (although it is used to prevent laziness in functions when doing fierce recursion) and hence we can use it in function names. There is already a function built into Haskell called exponent so adding the prime after our function denotes that it's a slightly modified version. This example won't work without it being there because Haskell will try to use the built in function.

image

For the next section, unload the script by typing
:m
at the
*Main>
prompt and pressing ENTER and you should have your
Prelude>
prompt back again. Lambda functions in Haskell are declared similarly to the way they are in Python but since they are generally used 'on the fly', we'll define them at the interactive prompt rather than in a script. Lambdas are prefixed with a back slash (\) because it looks like a Greek letter lambda (λ), I suppose.

Prelude> multiplier = \x n -> x*n
Prelude> doubler = multiplier 2
Prelude> doubler 4
8
Prelude> tripler = multiplier 3
Prelude> tripler 4
12
Prelude> halver = multiplier 0.5
Prelude> halver 4
2.0


Don't worry too much if you aren't really following this (I wouldn't be surprised). Later on when we look at the main higher order functions
map
,
filter
and
reduce
/
fold
, you'll see how these can be used in context.

image
Click if you wanna learn more...

time limit
Task 2.5 Haskell functions

Using the text editor and Haskell, your task is to try to construct some of the functions as you did using Python in Task 2.4. This will help you to practice constructing explicit function types in Haskell (which is like the ones you'll see on the examination!)

image

Create a Haskell function,
half
, which returns half of it's argument
Create a Haskell function,
reciprocal
, which returns the reciprocal of it's argument
Create a Haskell function,
polynomial
, which returns the solution to 2x2+3x-4
Using the existing Haskell function,
exponent'
(notice the prime), create a new Haskell function,
squareroot
, which returns the square root of it's argument
Create a new function,
incrementer
, which returns a lambda function taking two arguments,
m
and
n
, which returns
m + n
. Use this function to create two more lambda functions,
successor
and
predecessor
. (HINT : You have to surround negative numbers with brackets or else Haskell gets confused)

OUTCOME : Some groovy lambda functions.

Checkpoint

image

image
Yeah, right

Activity 3
Map, filter and fold

The three most common functions which facilitate a functional approach to programming in Python are
map
,
filter
and
reduce
. These three higher order functions form the backbone of most applications of functional programming in the field of big data, and they also make extensive use of the anonymous lambda functions we've just been learning about in the last activity (which is why we learnt about them!)

image
Ok, OK - here we go...

Map

The map function takes a list of values from a domain and applies a function to each value in the list, mapping the value to a range in the subdomain (remember them?)

image
The map function using a lambda function

image

The map function is built into the standard Python library so it's always available (noice). The blueprint is ...

map(function, *iterable) --> map object


The
map
object can be converted into list using the
list(object)
function. The function passed to
map
can be a built-in Python function. For instance, this
map
operation converts a string to a list of ASCII codes.

>>> list(map(ord,'Hello there!'))
[72, 101, 108, 108, 111, 32, 116, 104, 101, 114, 101, 33]


... and this one converts a list of numbers into a list of strings.

>>> list(map(str,[1,2,3,4,5]))
['1', '2', '3', '4', '5']


If the function takes more than one parameter, simply pass
map
multiple lists. For instance, for the power function,
pow(x,y)
which takes two arguments (and is equivalent to
x**y
) can be applied using a map function across two lists of values ...

>>> list(map(pow,[5,4,3,2,1],[1,2,3,4,5]))
[5, 16, 27, 16, 1]


Consider this mathematical delight ...

>>> import math
>>> list(map(math.sin,[34,54,65]))
[0.5290826861200238, -0.5587890488516163, 0.8268286794901034]


As you can imagine, you can also put lambda functions in the
map
function to achieve more complex functional application. For example a simple
map
function which squares every value in a list ...

>>> list(map(lambda x:x**2, [1,2,3,4,5]))
[1,4,9,16,25]


It's common to separate the function from the map operation, which would be especially useful if the function was more complicated. For instance ...

>>> double = lambda x:x**2
>>> list(map(double,[1,2,3,4,5]))
[1,4,9,16,25]


We can also design a map function which capitalises every letter in a string.

>>> ''.join(list(map(lambda x:chr(ord(x)-32),'hello')))
'HELLO'


The list is literally endless ...

time limit
Task 3.1 Map in Python

Write map functions for the following situations. I've given you a hint on the first four ...

Generate a list of the lengths of names in this list
names = ['Charles','Andrew','Bob','Leanne','Beatrice']
;
Generate a list of the hex codes for
[85,18,171,255,0]
;
Generate a list of the types for
[1,True,'Hello',12.44]
;
Generate a list of the absolute values for
[-5,12,-6,10,-7,-1]
;
Use a suitable lambda function to generate a list of the successors to
[1,2,3,4,5]
.
Use a suitable lambda function to add together the two vectors
[3,5,4]
and
[1,3,4]
.

image

OUTCOME : Evidence that you can use the map function in context.

Checkpoint

image

It's worth noting that the result of the map function can be achieved using list comprehensions. For instance, both these statements are equivalent ...

>>> list(map(lambda x:x+1,[1,2,3,4,5]))
[2,3,4,5,6]
>>> [x+1 for x in [1,2,3,4,5]]
[2,3,4,5,6]


image
Neither!

image

In Haskell, the map function is structured like this ...

map (function) [list]


The function can be a simple partial application like
(+1)
or a lambda function like
(\x -> x + 1)
. Both these functions are equivalent. Look ...

Predule> map (+1) [1,2,3,4,5]
[2,3,4,5,6]
Prelude> map (\x -> x+1) [1,2,3,4,5]
[2,3,4,5,6]


Haskell has many built in functions which we can use in the map function. For instance ...

Prelude> map (odd) [1,2,3,4,5]
[True,False,True,False,True]


Unfortunately, you can only provide one list as an argument to the Haskell
map
function, unlike Python, so you have to engage in some pretty funky tricks to get it to handle more complex situations where you might want to give the function multiple parameters.

Prelude> zip [5,7,6] [1,4,5]
[(5,1),(7,4),(6,5)]


The
zip
function takes two lists and creates a single list of tuples (you'll recognised the difference between lists and tuples from Python, I'm sure). Then, we can throw our list of tuples into the
map
function to do some groovy things.

Prelude> map (\(x,y) -> abs(x-y)) (zip [5,8,3] [6,6,6])
[1,2,3]
Prelude> map (\(x,y) -> maximum [x,y]) (zip [4,6,8] [1,2,9])
[4,6,9]


The first example generates a list of the absolute difference between related values in the lists and the second example generates a list of the maximum related values from the lists. Nice.

time limit
Task 3.2 Map in Haskell

Write map functions for the following situations in Haskell. I've given you a hint on some of them ...

Generate a list of the lengths of names in this list
names = ["Charles","Andrew","Bob","Leanne","Beatrice"]
;
Generate a list of the even Bools (i.e. true/false) for
[1,2,3,4,5]
;
Generate a list of the absolute values for
[-5,12,-6,10,-7,-1]
;
Generate a list of the predecessors to
[1,2,3,4,5]
. Show two different ways of accomplishing this; one using a partial function and the other using a lambda function.
Use a suitable lambda function and the
zip
operator to add together the two vectors
[3,5,4]
and
[1,3,4]
.
Use a suitable lambda function and the zip operator to find generate a list containing the minimum values of the corresponding values in
[4,6,8
] and
[1,2,9]
.

image

OUTCOME : Evidence that you can use the map function in context.

Checkpoint


image

Once again, it's worth noting that the same can be achieved using list comprehensions. We haven't looked at list comprehensions in Haskell yet (and neither are we going to), however, you should be able to apply simple patterns using these examples. Notice the left facing '
<-
' which means 'is drawn from'.

Prelude> [x+1 | x <- [1,2,3,4,5]]
[2,3,4,5,6]
Prelude> [maximum [x,y] | (x,y) <- (zip [4,6,8] [1,2,9])
[4,6,9]


image
You've already asked me that!

Filter

The
filter
function takes a list of values and applies a conditional filter to it using a lambda function. The value from the list will be returned if, and only if, the result of the function is
true
.

image
The filter function through the application of a lambda function

image

The filter function is also built into the Python standard library, so it's always available. The blueprint is ...

filter(function, iterable) --> filter object


Where the function is a conditional statement which returns
true
or
false
. Common uses include, extracting even numbers from a list ...

>>> list(filter(lambda x:x%2==0,range(1,21))
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]


... extracting odd numbers from a list ...

>>> list(filter(lambda x:x%2==1,range(1,21)))
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]


You can truncate a list based in the value ...

>>> list(filter(lambda x:x>0,range(-5,5))]
[1,2,3,4]


... and even use string operations to search for matching values in a list or words.

>>> names = ['Anne', 'Amy', 'Bob', 'David', 'Carrie', 'Barbara', 'Zach']
>>> list(filter(lambda s: s.startswith('B'), names)
['Bob', 'Barbara']
>>> list(filter(lambda s: 'a' in s.lower(), names)
['Anne', 'Amy', 'David', 'Carrie', 'Barbara', 'Zach']


image

time limit
Task 3.3 The filter function in Python

Use a combination of
list
,
filter
and
lambda
to answer the following problems ...

Write a
filter
function which returns every third item from a list of values from 12 to 57, inclusive;
Write a
filter
function which returns only items of type
bool
from the list
[1,2,True,3,4,False]
. You can use the function body type(x) is bool in the lambda function to achieve this.
Declare a list of names of various length ...

names = ['Odis','Jovan','Tinisha','Gerda','Ayesha','Dorsey','Elton','Laila','Petronila']

... and write a
filter
function which only contains names over 6 characters in length.

OUTCOME : Various applications of the filter function

Checkpoint


image

It is worth noting that you can achieve pretty much the same outcomes using list comprehensions. For instance, we can generate our list of even and odd numbers like this ...

>>> [x for x in range(1,21) if x%2 == 0]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
>>> [x for x in range(1,21) if x%2 == 1]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]


... which some programmers find easier / clearer.

image
This is getting silly. Stop asking me!

In Haskell, we use the
filter
function in a similar way. This is the blueprint ...

filter (predicate) [list]


... where the
predicate
is a conditional statements which returns
true
or
false
and is applied to each element of the list. If the predicate is
true
, the list value is returned, if it is
false
, it is not. The
filter
function returns a list of values which satisfy the predicate.

We can filter out all odd values from a list like this ...

Prelude> filter (even) [1,2,3,4,5]
[2,4]


Notice the result of the application of even in this case is very different to that of its use in the
map
function. The predicate can look like part of a conditional statement, as in these examples ...

Prelude> filter (>0) [-5..5]
[1,2,3,4,5]
Prelude> filter (==3) [-5..5]
[3]


image
... but perfectly valid as long as the ranges are predictable!

Both these predicates are statements not functions (technically,
>
and
==
are functions but we'll ignore that for now) and take parameters in a prefix fashion, i.e. before the operator. So
>0
is like
x>0
. So, these are equivalent to the following filter operations using lambda functions ...

Prelude> filter (\x -> x>0) [-5..5]
[1,2,3,4,5]
Prelude> filter (\x -> x==3) [-5..5]
[3]


Once again, it's worth noting that we can use list comprehensions in Haskell to achieve the same outcome ...

Prelude> [x | x <- [-5..5], x>0]
[1,2,3,4,5]
Prelude> [x | x <- [-5..5], x==3]
[3]


image
No, no no!

We could define a slightly more complicated (and totally pointless) even number filter in three slightly different ways (just to prove some points and introduce some other points) ...

a) Using the built in even function as the predicate. We've seen this before 😄

Prelude> filter (even) [1..10]
[2,4,6,8,10]


b) Using a lambda function. This uses the Haskell mod operator which prefixes it's arguments.

Prelude> filter (\x -> mod x 2 == 0) [1..10]
[2,4,6,8,10]


c) Using a lambda function. I've modified the mod operator to an infix operator by surrounding it with backticks.

Prelude> filter (\x -> x `mod` 2 == 0) [1..10]
[2,4,6,8,10]


d) Defining a separate function and using that as the predicate. Sometimes better, sometimes not.

Prelude> evens x = mod x 2 == 0
Prelude> filter (evens) [1..10]
[2,4,6,8,10]


e) Using function composition. You should have met function composition in Task 1.1...

Prelude> filter ((==0).(`mod` 2)) [1..10]
[2,4,6,8,10]


The predicate is evaluated from right to left. For each value in the list, the result of the
mod
operation is passed to the
==0
statement. If the output of that is
True
, the value is added to the filtered list.

 1 :  1 `mod` 2 = 1 -> 1 == 0 -> False
 2 :  2 `mod` 2 = 0 -> 0 == 0 -> True -> Add 2
 3 :  3 `mod` 2 = 1 -> 1 == 0 -> False
 4 :  4 `mod` 2 = 0 -> 0 == 0 -> True -> Add 4
 5 :  5 `mod` 2 = 1 -> 1 == 0 -> False
 6 :  6 `mod` 2 = 0 -> 0 == 0 -> True -> Add 6
 7 :  7 `mod` 2 = 1 -> 1 == 0 -> False
 8 :  8 `mod` 2 = 0 -> 0 == 0 -> True -> Add 8
 9 :  9 `mod` 2 = 1 -> 1 == 0 -> False
10 : 10 `mod` 2 = 0 -> 0 == 0 -> True -> Add 10


image

Even though we learnt that the open circle (∘) represents function composition, there is no open circle available to show this in Haskell (or any other language), so the dot is used. Sorry 😔

Task 3.4 The filter function in Haskell

Use the
filter
function from Haskell to solve the following problems ...

Write a
filter
function which returns every odd number from the list
[1..10]
;
Write a
filter
function which returns every third item from a list of values from 12 to 57, inclusive. You should do this in three different ways, using a
lambda
function, a separate function,
threes
and using function composition.
Declare a list of names of various length ...

names = ["Odis","Jovan","Tinisha","Gerda","Ayesha","Dorsey","Elton","Laila","Petronila"]

... and write a filter function to generate a list which only contains names over 6 characters in length. You should do this using a
lambda
function, a separate function,
long
, and using function composition.

OUTCOME : Various applications of the filter function in Haskell.

Checkpoint

Reduce / Fold

Finally, the
reduce
(or
fold
) function completes our toolkit of standard functional programming tools. Unfortunately, the
reduce
function is not built into the standard Python library so we need to import it ...

from functools import reduce


...before we can use it. Basically, reduce takes a list and, through the application of a function, reduces the list to a single value.

image
The reduce operation using a lambda function

image

The
reduce
function is not built into the Python standard library, so it requires importation before we can use it. The blueprint is ...

reduce(function, iterable[, initialiser])


The function takes two arguments due to the way it operates on the list. Consider this simple
reduce
operation which adds up a list of numbers.

>>> reduce(lambda x,y:x+y,[1,2,3,4,5],0)
15


The operation of
reduce
is essentially
(((((0+1)+2)+3)+4)+5)
folding from the left side of the list (technically a left fold). The fold operation is recursive. Starting with the (optional) initialiser, the function adds this to the first item in the list, then adds the result of this addition to the second value in the list and so on until only one value is left which it returns.

You can also use
reduce
to work out the maximum number in a list of values.

>>> reduce(lambda a,b:a if a>b else b, [45,12,102,14,9])
102


The
reduce
function also works with strings.

>>> names = ['British','Broadcasting','Corporation']
>>> reduce(lambda a,b:a+b[0],names,'')
'BBC'


Notice that the initialiser for this operation is an empty string. Try it without that and see what happens (lol).

time limit
Task 3.5 Reduce

Use a combination of
reduce
and
lambda
to solve the following problems.

Write a
reduce
function which finds the product of
[1,2,3,4,5]
;
Write a
reduce
function which finds the minimum number in the list
[45,12,102,14,9]
;
Write a
reduce
function to calculate the dot product of
[12,6,6]
and
[13,12,10]
;
Write a
reduce
function which generates an acronym for the North Atlantic Treaty Organisation.

image

OUTCOME : Applications for the reduce function

Checkpoint


image

There is no reduce function in Haskell 🙄; it's called
fold
. In fact there are two different fold functions in Haskell,
foldl
(fold left) and
foldr
(fold right). They both operate recursively eating up values from the left hand side and the right hand side of the list respectively.

The blueprint for the functions is ...

foldl (function) initialiser [List]
foldr (function) initialiser [List]


The initialiser gives a special variable called the accumulator its initial value. The
foldl
function recursively adds the accumulator to each item in the list whereas the
foldr
function recursively adds each item in the list to the accumulator (i.e. the reverse). READ THAT AGAIN.

Here is a nice way of looking at both situations based on a common food chain.

image
Demonstrating a left fold

In a left fold, the food chain is accumulated from the owl to the grass; from left to right. The owl eats the mouse, then the owl (which has eaten the mouse) eats the grasshopper and finally the owl (which has eaten the mouse and the grasshopper) eats the grass.

image
Demonstrating a right fold

In a right fold however, the food chain is accumulated from the grass to the owl; from right to left. The grasshopper eats the grass then the mouse eats the grasshopper (which has eaten the grass) then the owl eats the mouse (which has eaten the grasshopper which has eaten the grass). In any event, the owl is full up.

Enough about food. Consider these two real examples ...

Prelude> foldl (+) 0 [1,2,3,4,5]
15


In essence, the
foldl
evaluates as
(((((0+1)+2)+3)+4)+5) = 15


Prelude> foldr (+) 0 [1,2,3,4,5]
15


In essence, the
foldr
evaluates as
(1+(2+(3+(4+(5+0))))) = 15


In this instance, the order of the fold makes no difference to the result since addition is commutative (order is unimportant). However, subtraction is not and the following two functions yield very different results.

Prelude> foldl (-) 0 [1,2,3,4,5]
-15
Prelude> foldr (-) 0 [1,2,3,4,5]
3


Why does this happen? Let's evaluate the expression manually to find out ...

image

time limit
Task 3.6 Folding with Haskell

Use a the Haskell
foldl
function to solve the following problems.

Write a
foldl
function which finds the product (*) of
[1,2,3,4,5]
. (Initialiser : 1);
Write a
foldl
function which concatenates (++) the list
["H","E","L","L","O"]
. (Initialiser : "").

image

OUTCOME : Applications for the fold function

Checkpoint

image

image
Do not attempt this section unless you are ready for a challenge!

This situation gets even more complicated with function composition and we are really getting way out of our depth here! However, when we were using Python, we managed to generate an acronym for the British Broadcasting Corporation using a Lambda function. However, the same process in Haskell is slightly more confusing (but I feel duty bound to tell you about it). The
foldl
and
foldr
functions evaluate composed functions in the reverse order so, to get the same outcome, we have ...

Prelude> foldl (\acc a -> acc ++ take 1 a) "" ["British","Broadcasting","Corporation"]
"BBC"
Prelude> foldr (\a acc -> take 1 a ++ acc) "" ["British","Broadcasting","Corporation"]
"BBC"


However, trying to use normal function composition causes problems ...

Prelude> foldl ((++).(take 1)) "" ["British","Broadcasting","Corporation"]
"BCorporation"
Prelude> foldr ((++).(take 1)) "" ["British","Broadcasting","Corporation"]
"BBC"


The
foldl
function evaluates like this :

(take 1 (take 1 (take 1 "" ++ "British") ++ "Broadcasting") ++ "Corporation)
(take 1 (take 1 "British" ++ "Broadcasting") ++ "Corporation)
(take 1 "BBroadcasting" ++ "Corporation)
"BCorporation"


The
foldr
function evaluates like this :

(take 1 "British" ++ (take 1 "Broadcasting" ++ (take 1 "Corporation")))
(take 1 "British" ++ (take 1 "Broadcasting" ++ "C"))
(take 1 "British" ++ "BC")
"BBC"


... which explains it all, quite clearly!

image
... and no task!

Activity 4
List operations

Back to Haskell for this last section and the fabulous 'Learn You a Haskell for Great Good' - my favourite book!

Task 4.1 Learn me a Haskell

Go back onto the web version of Learn You a Haskell for Great Good and continue working through the first part of the first chapter up to but not including 'Texas Ranges'.

image

Again, use WinGHCi to carry out the exercises in the book, reading the explanations carefully. Don't worry about evidencing your progress but do answer the following questions in your notebooks.

How do you define a name in Haskell?
What is the difference between the
++
operator and the
:
operator?
Describe how you would prepend and append an item to an existing list.
What does
[[],[],[]]
mean?
Write a Haskell command to retrieve the 4th item from the list
[6,5,4,3,2]
.
How do list comparisons work in Haskell?
Draw a picture to represent the
head
,
tail
,
init
and
last
operations (don't use a caterpillar).
Describe the purpose of the following list functions ...
-
length
-
null
-
reverse
-
take
-
drop
-
maximum
-
minimum
-
sum
-
product
-
elem

OUTCOME : Descriptions of common list operations

Checkpoint

image

image
Hello little caterpillar!

Extension Activities

How about these?

Visit Haskell's documentation page to find a list of resources to help you learn Haskell.
Read and enjoy 'Learn you a Haskell for Great Good!' - Holy Sh*t!
Read this series of interesting articles on how to be a functional programmer.

What's next?

Before you hand your book in for checking, make sure you have completed all the work required and that your book is tidy and organised. Your book will be checked to make sure it is complete and you will be given a spicy grade for effort.

END OF TOPIC ASSESSMENT

Last modified: February 14th, 2024
The Computing Café works best in landscape mode.
Rotate your device.
Dismiss Warning