A Functional Programmers Workout
A Functional Programmers Workout
Peter Achten
([email protected])
Software Science,
Institute for Computing and Information Sciences,
Radboud University Nijmegen,
Heijendaalseweg 135, 6525 AJ Nijmegen, The Netherlands.
This booklet is a collection of exercises that cover the material taught in the functional
programming courses at the Radboud University. You can use them to test, train, and
improve your skills in functional programming and prepare yourself for the examination.
We use the Clean 2.4 distribution. You can download it for free from the site:
http://wiki.clean.cs.ru.nl/
The exercises in each chapter focus on a certain theme. First, you get yourself
acquainted with the Clean IDE to create and compile a project, and use its search
facilities (Chapter 1). This is followed by writing functions on the standard set of basic
types (Chapter 2), using the overloading mechanism of Clean and creating non-recursive
types yourself (Chapter 3), working with lists and list comprehensions (Chapter 4),
higher-order functions (Chapter 5), manipulating the console and files (Chapter 6),
create and manipulate recursive types yourself (Chapter 7), create correctness proofs
via equational reasoning (Chapter 8), and work with dynamic types (Chapter 9). The
exercises of the remaining chapters are somewhat different. In Chapter 10 you find a
number of exercises in which you create the brain of a footballer and assemble your team
to participate in the SoccerFun competition.
Within a chapter, the difficulty level of the exercises gradually increases. It is not
necessary to complete an entire chapter before proceeding to the next one. Some exercises
depend on other ones, also across chapters. In that case you find which other exercises
are follow-up exercises of a particular assignment.
All assignments have a name. This name coincides with the name of the ‘main module’
of the corresponding Clean implementation. At each exercise you find a reference to
the name of that main module and the Clean environment that is required to create
and compile the project. All main modules have been made available in an Exercises
folder that you can download from the course site as a .zip file. Extract this file into
your Clean directory in the Examples folder. Besides the main modules, the Exercises
folder also contains test modules. A test module with name Main Test.icl can be used to
automatically test the exported functions from module Main.icl. Follow the instructions
that can be found in Main Test.icl.
The Clean environment StdEnv (64) directs the compiler to the standard modules
of Clean, the so-called standard environment. These are necessary to perform basic
operations on numbers, booleans, characters, strings, lists, and arrays. The document A
Concise Guide to Clean StdEnv [Achten(2011)] describes the standard environment.
The syntax of Clean is very similar to the syntax of Haskell. In Clean for Haskell98
Programmers - A Quick Reference Guide - [Achten(2007)] you can find a comparison of
the language features displayed on one page for easy reference.
This workout of functional programming exercises is regularly edited to match the
iii
Preface
material of the functional programming courses, and to improve and add exercises and
test files. We invite you to submit remarks, questions, tips, spotted errors, and so on, in
order for us to improve the assignments for future versions.
Acknowledgements The assignments, test files, and main modules were originally
written in Dutch. It started in 2006 as a small collection of exercises and former exam
questions of the functional programming courses and its many incarnations that I have
had the pleasure to teach with Rinus Plasmeijer. It has been expanded on during the
subsequent years. During this period my student assistants have been very helpful in
giving feedback and suggesting corrections and improvements. Marc Schoolderman in
particular has suggested and added many improvements and test files and cases. In 2016
it has been decided to translate the collection to English. For this effort I have been
fortunate to rely on the assistance of Thomas Churchman, Mart Lubbers, Camil Staps,
and Thom Wiggers. They have translated all exercises, main and test files of Chapters 1
upto 10.
Last but not least, I wish you a lot of fun while doing your functional workout.
Peter Achten
[email protected]
iv
Bibliography
v
BIBLIOGRAPHY BIBLIOGRAPHY
vi
Contents
vii
CONTENTS CONTENTS
viii
Chapter 1
Start
The exercises in this chapter serve to assist you in using the Clean IDE. They familiarize
you with the various windows (such as project, editor, types, errors) that are used in the
programming environment. Furthermore, it introduces useful navigation functionality,
such as finding modules, functions, and types.
Tips:
• You can find the user manual for the Clean IDE with the
command “Help:Help:UserManual.pdf”.
• Line numbers are not shown with the default settings
of the Clean IDE. It is useful to enable them on account
of error messages. You can enable line numbers with
the command: “Defaults:Window Settings:Editor Set-
tings. . . ”. Tick the box “Show LineNrs”.
import StdEnv
Start = expr0
1
1.2. SEARCHING IN THE CLEAN IDE CHAPTER 1. START
Add the following expri = . . . definitions from below step by step, and replace Start =
expri. Afterwards run “Project: Update and Run (Ctrl+R)”. Explain what happens in
every step.
expr1 = "Hello " +++ "World!"
expr2 = 5
expr3 = 5.5 • When one of the expressions gives a type
expr4 = 5 + 5.5 error you can put it into comments by pre-
expr5 = [1..10] fixing it with // (as in C).
expr6 = (expr1, expr2, expr3, expr5)
expr7 = [expr1, expr2, expr3, expr5] • You can quickly jump to the location of the
expr8 = [1, 3 .. 10] error with the command “Search:Goto Next
expr9 = [’a’ .. ’z’] Error. . . (Ctrl+E)”, or by double-clicking
expr10 = [’a’, ’c’ .. ’z’] the error message.
expr11 = [’Hello World!’]
2
CHAPTER 1. START 1.2. SEARCHING IN THE CLEAN IDE
Modules reside in directories. You can show and hide modules from a directory by
double clicking the directory name. By double clicking a module name the corresponding
definition module opens, and with a shift double click on a module name you open
the implementation module. With this method you can quickly find all definition and
implementation modules.
If you prefer opening the implementation module without holding shift, you can
configure this using the project window options (command “Defaults:Window Settings:-
Project Window. . . ”).
You can switch between implementation (icl) and definition modules (dcl) swiftly
by pressing Ctrl+/.
Exercise: Activate the scrabble project window and open the following modules:
Definitions in a project
Every Clean module consists of type definitions and function definitions. Modules can
use definitions from other modules by importing the definition module that exports those
definitions. In a module you can only use definitions created there yourself, or definitions
that you imported. There are several ways to find definitions.
Within a module you can quickly find a definition by pressing Crtl+‘. This opens a
pop up menu displaying all (top-level) definitions in alphabetical order. The Clean IDE
will place the cursor at the start of the line of the selected definition.
The command Crtl+= lets you search for a specific name (identifier ) in all definition
modules (Find Definition), implementation modules (Find Implementation) or identifiers
(Find Identifier). You can limit your search to only the imported modules (Search in
Imported Files), the project (Search in Project), or project paths (Search in Paths).
If you have selected a complete name (e.g. with a double click) you can find the
definition module exporting that name by pressing Crtl+L, and the implementation
module by pressing Crtl+Alt+L.
Exercise: Perform the following search queries:
1. Find the definition module exporting isEven. Which module did you find?
2. Activate the main scrabble module. Find all occurrences of the identifier isEven
within the imported modules. How many did you find?
3. Activate the scrabble module language.icl. Again find all the occurrences of isEven
within the imported modules. How many did you find?
3
1.2. SEARCHING IN THE CLEAN IDE CHAPTER 1. START
paths quickly by choosing a different environment. You can change project paths in the
Project Options dialog (Project:Project Options. . . ).
Exercise: Open the Project Options dialog and select the Project Paths radio button.
Remove {Project}\Engels and add {Project}\Nederlands. Close the dialog. Recompile
the entire application by pressing Ctrl+Shift+U. If all went well, the scrabble you
have just compiled is in Dutch.
4
Chapter 2
Simple Functions
The exercises in this chapter are practices for reading and working with functional
expressions (rewrite). A developed skill in working with expressions is essential for
reasoning about functions and making correctness proofs (this will be thoroughly treated
in Chapter 8). Additionally, this chapter contains exercises with a few classical standard
functions (function composition, currying, flip), with which existing functions can be
combined to form new functions. The remaining exercises are meant to illustrate how
you can express recursive functions in a functional style.
2.1 Notation
Main module: NotationFunctions.icl
Environment: StdEnv
Describe what each of the following Clean functions mean. Check your answer by adapting
the Start line to call the corresponding function, with valid arguments if necessary. The
function call a +++ b glues string b after string a; a rem b computes the remainder after
integer division of a with b; fst (a,b) = a; snd (a,b) = b.
f1 :: Int f4 :: Int Int -> Int
f1 =1+5 f4 x 0 = x
f4 x y = f4 y (x rem y)
f2 :: Int Int -> Int
f2 m n f5 :: (Int,Int) -> Int
| m < n =m f5 x = fst x + snd x
| otherwise = n
f6 :: (a,b) -> (b,a)
f3 :: String Int -> String f6 (a,b) = (b,a)
f3 s n
| n <= 0 = "" f7 :: (a,a) -> (a,a)
| otherwise = s +++ f3 s (n-1) f7 x = f6 (f6 x)
5
2.3. GCD CHAPTER 2. SIMPLE FUNCTIONS
Determine the result of the expressions below by rewriting. First add parentheses () in
the expressions illustrating the priorities of the operations correctly. Then apply rewrites
as shown in the lecture: start from Start = ei , place every rewrite step on a new line, and
underline the part of the expression that you rewrite (the redex, reducible expression).
e1 = 42
e2 = 1 + 125 * 8 / 10 - 59
e3 = not True || True && False
e4 = 1 + 2 == 6 - 3
e5 = "1 + 2" == "6 - 3"
e6 = "1111 + 2222" == "1111" +++ " + " +++ "2222"
2.3 gcd
Main module: RewriteRecursion.icl
Environment: StdEnv
The module StdInt implements the greatest common divisor function in the following
way (the definition in StdInt is a bit different):
instance gcd Int where
gcd :: Int Int -> Int
gcd x y = gcdnat (abs x) (abs y)
6
CHAPTER 2. SIMPLE FUNCTIONS 2.5. TYPE INFERENCE AND STRINGS
f1 x =x
f2 x
| x < 0 = -1
| x > 0 = 1
| otherwise = 0
f3 x = (snd x, fst x)
f4 (x,y) = (y,x)
f5 x y = (x,y)
f6 x y = (x,(y,y),x)
f7 (x,(y,z)) = ((x,y),z)
f2 x y = f1 x y 0
f3 x y
| y < 0 || y >= size x = x
| isUpper x.[y] = f3 (x := (y,toLower x.[y])) (y+1)
| isLower x.[y] = f3 (x := (y,toUpper x.[y])) (y+1)
| otherwise = f3 x (y+1)
f4 x = f3 x 0
f5 x y z
| y > z = x
| y < 0 = x
| z >= size x = x
| otherwise = f5 (x := (z,x.[y]) := (y,x.[z])) (y+1) (z-1)
7
2.6. MATCHING STRINGS CHAPTER 2. SIMPLE FUNCTIONS
f6 x = f5 x 0 (size x - 1)
1. Write the two functions head :: String -> Char and tail :: String -> String that
given a non-empty String return the first and the remaining elements of the String
respectively. If the argument is the empty String, it should show an error.
Example: head "Madam, I’m Adam" = ’M’.
Example: tail "Madam, I’m Adam" = "adam, I’m Adam".
2. Write a function is_equal that determines the equality of two String arguments.
This means that for every pair s1 , s2 of type String the following should hold:
is_equal s1 s2 = s1 == s2 .
3. Write a function is_substring that returns a Bool when given two String arguments.
The result should be True if and only if the first argument is a substring of the
second argument.
Example: is_substring "there" "Is there anybody out there?" 1 returns True, after
all: "Is there anybody out there?".
Example: is_substring "there" "Just for the record" 2 returns False because there
is a space between the and re.
1 Pink Floyd – The Wall (1979)
2 Marillion – Clutching at straws (1987)
8
CHAPTER 2. SIMPLE FUNCTIONS 2.7. DETERMINING PRIME NUMBERS
4. Write a function is_sub that returns a Bool when given two String arguments. The
results should be true True if and only if the characters of the first argument appear
in the same order in the second argument. Characters of the second argument
may be skipped.
Example: is_sub "there" "Just for the record" returns True because the space will
be skipped.
Example: is_sub "she and her" "Is there anybody in there?" returns True, after all:
"Is there anybody in there?".
Example: is_sub "There there" 3 "Is there anybody in there?" returns False because
T is no part of the second String.
5. Write a recursive function is_match that returns a Bool when given two String
arguments (pattern and source). The function determines whether the pattern
can be applied on the source (the pattern matches the source). The pattern may
contain wildcard characters:
All other characters in the pattern have to match exactly with the corresponding
characters in the source.
Example: is_match "here" "here" returns True.
Example: is_match "here" " here" returns False because the first characters of the
two strings do not match.
Example: is_match "here" "here " returns False because the second text has one
extra character.
Example: is_match ".." "AB" returns True because the second text consists of ex-
actly two characters.
Example: is_match "*?" "Is there anybody in there?" returns True because the second
text ends with a ’?’ character.
Example: is_match "*?*!*?*!" "Answers? Questions! Questions? Answers!" 4 returns
True.
Example: is_match "*.here*.here*." "Is there anybody in there?" returns True.
Example: is_match ".here.here." "Is there anybody in there?" returns False.
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79,
83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163,
3 Radiohead – Hail to the thief (2003)
4 Focus – Focus III (1973)
9
2.8. PRIME FACTORS CHAPTER 2. SIMPLE FUNCTIONS
167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443,
449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557,
563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647,
653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757,
761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983,
991, 997]
Note: Use the function rem for integer division with remainder. The function mod exists,
but it is not defined for integers.
10
CHAPTER 2. SIMPLE FUNCTIONS 2.10. O TANNENBAUM
2.10 O Tannenbaum(used in 6 .2 )
Main module: OTannenbaum.icl
Environment: StdEnv
1. Write the function triangle that receives an Int argument n. This
*
function will draw a triangle as shown on the right with n = 5. The ***
drawing is actually returning a String of which every line ends with a *****
newline. *******
Hence, the string corresponding with the image to the right is: *********
" *\n ***\n *****\n *******\n*********\n"
2. Write the function christmastree that receives an Int argument n. This *
function draws a Christmas tree as a String (as shown on the right with *
n = 4). For every sub triangle the function should use a generalized ***
version of the above defined triangle function. *
***
*****
*
***
*****
*******
The following operations on Strings and Chars are useful: s1 +++ s2 glues String s2 to
String s1 ; toString c creates a String value from a Char c. ‘\n’ is the newline character.
To get program output without the String quotation marks you can select the option
“Basic Values Only” in the Clean IDE dialog “Project:Project Options. . . ”.
Additionally, you find a number of functions (f1 . . . f4, g1 . . . g4) that use these
instances. For each function, rewrite the program that starts with that function (so,
Start = f1 . . . Start = g4).
11
2.13. TRAJECTORY CHAPTER 2. SIMPLE FUNCTIONS
∀a : zero + a = a = a + zero
∀a : a - zero = a = ~ (zero - a)
∀a : one * a = a = a * one
∀a : zero * a = zero = a * zero
∀a : a / one = a
∀a : ~ (~ a) = a
You can easily test this by adding a Start line that calls the function test for several
values of type (,) and (,,):
test a = ( zero + a == a && a == a + zero
, a - zero == a && a == ~ (zero - a)
, one * a == a && a == a * one
, zero * a == zero && zero == a * zero
, a / one == a
, ~ (~ a) == a
)
2.13 Trajectory
Main module: Trajectory.icl
Environment: StdEnv
An object, such as a ball, which is shot away from a flat surface with an angle of
θ0 (0 < θ0 ≤ π2 ) 5 and initial velocity v0 (0 < v0 ), follows an arclike trajectory influenced
by gravity 6 . If we ignore effects such as air resistance and wind, the trajectory of the
ball is a curve defined in a vertical flat surface in which the x-axis shows the distance to
the starting position on the ground and the y-axis shows the height of the ball.
Because we ignore air resistance, the velocity of the ball in the x-direction (vx (t)) is
constant. The velocity of the ball in the y-direction (vy (t)) depends on the time t and
the gravity g = 9.81m/s2 :
vx (t) = v0 · cos(θ0 )
vy (t) = v0 · sin(θ0 ) − g · t
You can calculate the distance x(t) and the height y(t) of the ball on time t as follows:
x(t) = v0 · cos(θ0 ) · t
1
y(t) = v0 · sin(θ0 ) · t − · g · t2
2
Height h, expressed in distance x is:
g
h(x) = tan(θ0 ) · x − · x2
2 · (v0 · cos(θ0 ))
The ball reaches the highest point at time tmax y :
v0 · sin(θ0 )
tmax y =
g
5 Angle
in radians.
6 Source:
Halliday, Resnick. Physics, Parts I and II, combined edition, Wiley International Edition,
1966, ISBN 0 471 34524 5
12
CHAPTER 2. SIMPLE FUNCTIONS 2.14. THIS OLD MAN
Thus you can find the maximal height reached at that time by plugging tmaxy into
y(t). The ball takes just as much time to reach the highest point as it takes to get back
on the ground. Therefore the ball is airborne for t2 = 2 · tmax y seconds. You can find the
distance the ball travels by plugging t2 into x(t). The velocity of the ball when touching
the ground can be found by plugging t2 into vx (t) and vy (t). Then, the combined value
is: q
2 2
v1 = vx (t2 ) + vy (t2 )
13
2.14. THIS OLD MAN CHAPTER 2. SIMPLE FUNCTIONS
Try to make the program as short as possible by abstracting away from patterns in
the text and writing suitable functions for those.
The following operations on Strings and Chars are useful: s1 +++ s2 glues String s2 to
String s1 ; toString c creates a String representation of a Char c; toString t creates a String
representation of an Int n; ‘\n’ is the newline character (type Char), "" is the empty
string (type String). To suppress the String quotation marks in the output you can select
in the Clean IDE dialog “Project:Project Options. . . ” the option “Basic Values Only”.
14
CHAPTER 2. SIMPLE FUNCTIONS 2.15. STRINGNUM
The following functions on Strings and Chars are useful: isDigit c tests whether a
Char c is a digit; toInt c returns the ascii-code of Char c; size s returns the length of the
String s; +++ glues two Strings together; s.[i] returns the character from s at index i;
s%(i,j) returns the slice from s from index i up to but not including j; s:=(i,c) replaces
the character in s at index i with c.
15
CHAPTER 2. SIMPLE FUNCTIONS
Time
• parsetime x: with x a string, this function attempts to extract the format explained
above and return the corresponding (positive or negative) number of seconds. If x
is ill formatted, then the result should be zero.
16
Chapter 3
Non-recursive Types
Algebraic types and records are the eminent tool for modeling new data structures
in functional languages. New data structures play an essential role with overloading,
as it is type-driven. Algebraic types enable you to introduce new constants in your
programs, generally increasing the readability and correctness. Record types allow you
to manipulate collections of values without knowing exactly where the values reside in
the collection, nor where they are defined.
f2 Monday =1
17
3.3. RECORD NOTATIONS CHAPTER 3. NON-RECURSIVE TYPES
f2 Tuesday = 2
f2 Wednesday = 3
f2 Thursday = 4
f2 Friday = 5
f2 Saturday = 6
f2 Sunday = 7
f3 x y = f2 x == f2 y
f4 x = Nat x
f5 (Nat x) =x
f6 x
|x>0 = Pos x
| otherwise = NotAPosNat
f8 (Pos x) (Pos y)
|x>y = Pos (x-y)
| otherwise = NotAPosNat
f2 = {given="", suffix="",family="",postfix=""}
18
CHAPTER 3. NON-RECURSIVE TYPES 3.5. DAY
f4 p=:{gender=Male}
| p.name.given == p.father.given = True
f4 p=:{gender=Female}
| p.name.given == p.mother.given = True
f4 _ = False
f5 p
| f4 p = {p & name = {p.name & postfix = "junior"}}
| otherwise = p
Write the following functions and test them with an appropriate Start-function:
1. friday_on_my_mind :: Day -> Bool which yields True only if the argument is Friday.
2. is_weekend :: Day -> Bool which yields True only if the argument is Saturday or Sunday.
3. (on_my_mind) :: Day Day -> Bool which yields True only if the two arguments are equal.
For instance, Friday on_my_mind d is True only if d = Friday.
4. yesterday :: Day -> Day and tomorrow :: Day -> Day that return the previous day and
next day of their argument respectively.
19
3.7. FRACTIONS CHAPTER 3. NON-RECURSIVE TYPES
Optional: If you did Exercise 3.7, you can also add rational numbers to
this implementation module.
In the implementation you need to choose the most precise representation, thus you
should not just convert values to Reals. The following operations on Num values should be
supported:
• The functions equalNum and smallerNum test whether two fractions are equal or smaller
respectively.
• The operations plusNum, decrementNum, timesNum, and divideNum implement the math-
ematical operations +, −, ·, and ÷.
• Just like integers and reals, numerals are signed numbers (can be negative, positive,
or zero). absoluteNum takes the absolute value, signOfNum returns the sign of the
argument and negateNum flips the sign.
20
CHAPTER 3. NON-RECURSIVE TYPES 3.9. CARD
Can you also make sure that the following holds for every String s?
3.10 StdDay
Main module: StdDay.icl
Environment: StdEnv
In exercise 3.5 the type Day and a number of operations have been defined. Use this
implementation in the module StdDay to make the operations available as instances of the
overloaded operators == (on_my_mind), and the overloaded functions previous (yesterday)
and next (tomorrow) that are defined in module PrevNext.
3.11 StdDate
Main module: StdDate.icl
Environment: StdEnv
In exercise 3.6 the types Date and Month and a number of operations have been defined.
Use this implementation in the module StdDate to make the operations available as
instances of the overloaded operators ==, < and the overloaded functions previous and
next that are defined in module PrevNext.
3.12 StdQ
Main module: StdQ.icl
Environment: StdEnv
In exercise 3.7 you have defined a type Q to manipulate fractions. Use this implementation
21
3.13. STDNUM CHAPTER 3. NON-RECURSIVE TYPES
in the module StdQ to make the operations available as instances of the overloaded
operators == (equalQ), < (smallerQ), + (plusQ), - (decrementQ), * (timesQ), / (divideQ), abs
(absoluteQ), sign (signOfQ), ~ (negateQ).
Furthermore, module StdQ adds the following new instances: zero, one, toInt, toReal,
toQ, and toString. All together, the following properties should hold:
• The operation == and < test equality and less-than.
• The operations +, -, * and / are the usual arithmetical operations on the rational
numbers. The values zero and one are the neutral elements of addition and
multiplication respectively.
• abs takes the absolute value, sign returns the sign of the argument and ~ flips the
sign.
Warning: be aware that the absolute value of min-int is equal to min-int!
• isInt tests whether the argument corresponds to a whole number. If this is true,
the Q instance for toInt results in exactly the same number (no rounding). toReal
converts the rational number to a Real (by approximation).
The instances of the class toQ have the reverse functionality: Int and Real convert
the argument of their type (by approximation) to a rational number. For the
(Int, Int) instance, toQ (t, n) should create the rational number nt . It throws an
error message in case n = 0. For the (Int, Int, Int) instance, toQ (d, t, n) should
create the rational number d nt (assuming d 6= 0 and 0 < t < n). It throws an error
message in case d = 0 or t ≤ 0 or n ≤ 0 or n ≤ t.
In your implementation you can use the Int instance of the function gcd, which
calculates the greatest common divisor of two positive integers. This function is defined
in the StdInt module, which you get automatically when you import StdEnv.
3.13 StdNum
Main module: StdNum.icl
Environment: StdEnv
In exercise 3.8 you have defined a type Num to manipulate various kinds of numbers: whole
numbers, floating point numbers, and, optionally, fractions. Use this implementation in
the module StdNum to make the operations available as instances of the overloaded oper-
ators == (equalNum), < (smallerNum), + (plusNum), - (decrementNum), * (timesNum), / (divideNum),
abs (absoluteNum), sign (signOfNum), ~ (negateNum).
Furthermore, module StdNum adds the following new instances: zero, one, toInt, toReal,
fromNum, toNum, and toString. If you have included fractions in exercise 3.8, then StdNum
also implements toQ.
22
CHAPTER 3. NON-RECURSIVE TYPES 3.15. OVERLOADING AND RECORDS
Implement the classes ==, zero, one, +, -, *, /, and ~ for the new type in a similar
manner as in Exercise 2.12.
3.16 StdStringnum
Main module: StdStringnum.icl
Environment: StdEnv
In Exercise 2.15 you created a type and operations for arbitrary large non-negative
integers. Use this implementation in the module StdStringnum to make the operations avail-
able as instances of the overloaded operators < (smaller), == (equal), + (plus), - (decrement),
* (times), and / (divide). To this end, define a new type Stringnum in StdStringnum.icl:
:: Stringnum = Stringnum String
Also define instances for the overloaded functions zero and one, and the conversion
functions fromInt, toString, and fromString.
3.17 StdStringnum2
Main module: StdStringnum2.icl
Environment: StdEnv
In Exercise 2.16 you created a type and operations for arbitrarily large integers (negative,
positive, and zero). Use this implementation in the module StdStringnum2 to make the
operations available as instances of the overloaded operators abs (absolute), ~ (changeSign),
sign, < (smaller2), == (equal2), + (plus2), - (decrement2), * (times2), and / (divide2). To this
end, define a new type Stringnum2 in StdStringnum.icl.
:: Stringnum2 = Stringnum2 String
Also define instances for the overloaded functions zero and one, and the conversion
functions fromInt, toString, and fromString.
23
3.18. STDTIME CHAPTER 3. NON-RECURSIVE TYPES
24
Chapter 4
Lists
Lists are one of the most widely used recursive data types in functional languages. The
exercises in this chapter teach you how to create and manipulate lists, and how to work
with so-called list comprehensions. Lists make extensive use of the lazy nature of Clean.
This enables the programmer to work with potentially infinite lists and to postpone
certain calculations that they might need in the future.
• give the shortest notation that results in an equal list (this could be the same
expression); and
• give the result of the standard functions hd, tl, init, and last on the list. You can
find these functions in the module StdList.icl.
25
4.2. TYPES AND VALUES CHAPTER 4. LISTS
e2 :: [Bool]
e2 = ...
e3 :: [[Int]]
e3 = ...
e4 :: [[[Real]]]
e4 = ...
f2 = [[]]
f3 x = [x]
f4 (x,y) = [x,y]
f5 (x,y) = [(x,y)]
f6 x = [[x]]
f7 [x,y:z] = (x,y)
f8 [x:y] = (x,y)
f9 [x] =x
f9 [x,y] = x
f9 [x:xs] = f9 (init xs)
26
CHAPTER 4. LISTS 4.4. THE FIRST WILL BE THE LAST
Calculate In this exercise you calculate the results of a number of functions using
rewriting with pen and paper. The functions used are first2 and last2, or are from
StdEnv. Write the rewrite steps in the same way as presented in the lecture: start
from Start, start every step on a new line, and underline the part you are going to
rewrite. Examples can be found in the lecture slides.
First n and last n Write the recursive functions firstn and lastn that both get a
number and a list as argument and return a list. The type of both functions thus
is: Int [a] -> [a]. The function first n lst returns the first n elements of list lst
and lastn n lst returns the last n elements of list lst. Generate an error using the
function abort when the argument lists are too short.
Examples:
firstn 4 [42] = "List is too short."
firstn 4 [1,2,3,4,5] = [1,2,3,4]
lastn 4 [42] = "List is too short."
lastn 4 [1,2,3,4,5] = [2,3,4,5]
27
4.5. GLUEING LISTS WITH ++ CHAPTER 4. LISTS
Calculate the results of the expressions below by rewriting the ++ operator. The
values of xi are irrelevant. Part 6 is challenging!
1. [] ++ []
2. [] ++ [x0,x1] ++ []
3. [[]] ++ [x0,x1]
4. [x0,x1] ++ [[]]
5. [x0,x1,x2] ++ ([x3,x4] ++ ([x5] ++ []))
6. (([x0,x1,x2] ++ [x3,x4]) ++ [x5]) ++ []
Use the results of part 5 and 6 to argue why the ++ operator is right associative.
Calculate the results of the expressions below by rewriting the flatten and ++ functions.
The values of xi are irrelevant. Use the results found in 2 in part 3.
1. flatten [[x0,x1,x2], [x3,x4], [x5], []]
2. flatten [[[x0,x1,x2], [x3,x4]], [], [[x5], []]]
3. flatten (flatten [[[x0,x1,x2], [x3,x4]], [], [[x5], []]])
28
CHAPTER 4. LISTS 4.8. ZF-NOTATIONS
4.8 ZF-notations
Main module: NotationZF.icl
Environment: StdEnv
For all the functions below explain what they calculate, and additionally give the most
general type of each of the functions.
g1 as bs = [(a,b) \\ a <- as, b <- bs]
g2 as bs = [(a,b) \\ a <- as & b <- bs]
g3 as bs = [(a,b) \\ a <- as, b <- bs | a <> b]
g4 as bs = [ a \\ a <- as, b <- bs | a == b]
g5 xss = [ x \\ xs <- xss, x <- xs]
g6 a xs = [ i \\ i <- [0 ..] & x <- xs | a == x]
29
4.10. !! AND ?? CHAPTER 4. LISTS
4.10 !! and ??
Main module: ZFSearch.icl
Environment: StdEnv
The operator (!!) infixl 9 :: ![a] !Int -> a from StdList selects the ith element from a
list xs after calling xs !! i (counted from zero). If xs does not contain enough elements
an error message is generated.
Implement using list comprehensions the operator
(??) infixl 9 :: ![a] !a -> Int | Eq a
that searches for the index of x in a list xs, if x is a member of xs. If there is no such
value, then xs ?? x should return -1. If xs is a list containing element x, then the
following must hold: xs !! (xs ?? x) = x. If x is not an element of xs, then xs !! (xs ??
x) results in an error message.
Examples:
[1,2,3,4,5,6] ?? 3 = 2
[1,2,3,4,5,6] ?? 10 = -1
[’Hello world’] ?? ’o’ = 4
4.13 Fibonacci
Main module: Fibonacci.icl
Environment: StdEnv
30
CHAPTER 4. LISTS 4.14. PREFIXES
Write the recursive function fibonacci as above with the meaning: fibonacci n = Fn . For
which values is this function defined?
Test your function with incrementing values:
N = 46
Start = [fibonacci i \\ i <- [1 .. N]]
4.13.2 Sumlists
Write the function sumLists with the following meaning:
You need to choose your own behaviour for lists that are of unequal length. Motivate
that choice. Test your function with some representative sample inputs. Write what
inputs you chose and give the result.
Describe and explain the execution behaviour of the program. Compare it to the
behaviour of the program from 4.13.1.
4.14 Prefixes(used in 6 .3 )
Main module: Firsts.icl
Environment: StdEnv
The function firsts computes all prefixes of a list xs:
firsts [ ] = [[ ]]
firsts [x0 . . . xn ] = [[ ], [x0 ], [x0 , x1 ], [x0 , x1 , x2 ] . . . [x0 . . . xn ]] (for n ≥ 0)
31
4.15. POSTFIXES CHAPTER 4. LISTS
2. If xs is a list of n elements, how many elements does the list (firsts xs) have?
3. Implement firsts. The result of this function should have the same elements as
above. The order of the elements may be different.
4.15 Postfixes(used in 6 .3 )
Main module: Lasts.icl
Environment: StdEnv
The function lasts computes all postfixes of a list xs:
lasts [ ] = [[ ]]
lasts [x0 . . . xn ] = [[x0 . . . xn ] . . . [xn−2 , xn−1 , xn ], [xn−1 , xn ], [xn ], [ ]] (for n ≥ 0)
4.16 Fragments(used in 6 .3 )
Main module: Frags.icl
Environment: StdEnv
The function frags computes of a list xs all fragments. A fragment of xs is defined as a
part of xs such that it only contains consecutive elements of xs. The number and the
position of the first element is arbitrary (but of course limited by xs). Note that both []
and xs itself are fragments of xs. For example:
frags [ ] = [[ ]]
frags [x0 . . . xn ] = [[ ]
, [x0 ] . . . [xn ]
, [x0 , x1 ], [x1 , x2 ], . . . [xn−1 , xn ]
..
.
, [x0 . . . xn−1 ], [x1 . . . xn ]
, [x0 . . . xn ]
] (for n ≥ 0)
32
CHAPTER 4. LISTS 4.17. SUBLISTS
4.17 Sublists(used in 6 .3 )
Main module: Subs.icl
Environment: StdEnv
The function subs computes of a list xs all sublists. A sublist of xs is a list with the same
elements in the same order, but in which an arbitrary number of elements is left out.
Note that both [] and xs are sublists of xs. For example:
subs [] = [[ ]]
subs [x0 ] = [[ ], [x0 ]]
subs [x0 , x1 ] = [[ ], [x0 ], [x1 ], [x0 , x1 ]]
subs [x0 , x1 , x2 ] = [[ ], [x0 ], [x1 ], [x2 ], [x0 , x1 ], [x0 , x2 ], [x1 , x2 ], [x0 , x1 , x2 ]]
perms [] = [[ ]]
perms [x0 ] = [[x0 ]]
perms [x0 , x1 ] = [[x0 , x1 ], [x1 , x0 ]]
perms [x0 , x1 , x2 ] = [[x0 , x1 , x2 ], [x0 , x2 , x1 ]
, [x1 , x0 , x2 ], [x1 , x2 , x0 ]
, [x2 , x0 , x1 ], [x2 , x1 , x0 ]
]
1. Give the most general type of perms.
2. If xs is a list of n elements, how many elements does the list (perms xs) have?
3. Implement perms. The result of this function should have the same elements as
above. The order of the elements may differ.
4.19 Partitions
Main module: Partitions.icl
Environment: StdEnv
The function partitions computes all partitions of a list xs. A partition of xs is a list
[A1 , . . . , An ] (n ≥ 0), such that A1 ++ . . . ++ An = xs and every Ai is non-empty. The
empty list only has itself as possible partition. For example:
33
4.20. LISTS AND SORTING CHAPTER 4. LISTS
Testing for sorted Use the idea from exercise 4.13.3 to construct a function is_sorted
that takes a list and tests if the list is sorted. The function is_sorted may only
inspect the list using list comprehension.
Sorting lists The list L0 is the sorted list of L if L0 is a permutation of L and L0 is
sorted. Write the function zfsort that takes a list and sorts it according to this
definition.
Use the function perms from exercise 4.18, your function is_sorted and only a list
comprehension.
Complexity What is the order of complexity of this way of sorting in terms of the
length of the list? Is it a good way to sort lists?
With the showFrequencylist function from FrequencylistGUI we can visualise the result.
Example:
34
CHAPTER 4. LISTS 4.22. THE LAST WILL BE THE FIRST
import FrequencylistGUI
Another way is by noting that the last element of the list is the same as the first element
of the reversed list (you may find the function reverse in module StdList):
last2 :: ([a] -> a)
last2 = hd o reverse
Compute Rewrite the following expressions by writing each step on a new line, and by
underlining the redexes that you rewrite.
1. Start = last1 [1,2,3,4]
2. Start = last2 [1,2,3,4]
35
4.23. NUMBER SEQUENCE CHAPTER 4. LISTS
Function equality versus execution behaviour The functions last1 and last2 are
equal, that is: given the same arguments they give the same result. That does not
mean they are the same. Explain the difference using the results from the previous
question.
• The operation toSet converts a list of elements to a set. Sets do not contain
duplicate elements, so the list that is obtained from fromSet may not contain
duplicates.
• The predicates isEmptySet, isDisjoint and memberOfSet respectively test if a given set
is empty (contains no elements), two sets are disjoint (have no common elements),
and if a given value is a member of a given set (is contained by the set).
36
CHAPTER 4. LISTS 4.26. STACK
• The Set instance of zero gives the empty set. The empty set has no elements.
The Set instance of == tests if two sets are identical (contain the same elements).
Note that in sets, the order of the elements is not defined! This means that
{1, 2} = {2, 1}. The function numberOfElements returns the number of elements of a
set.
• There are two predicates that test a subset-relationship. The predicate isStrictSubset
determines if the first argument is a strict subset of the second argument (i.e. the
second argument contains elements that do not occur in the second argument,
A ⊂ B). The predicate isSubset determines if the first argument is a subset of the
second argument (all elements of the first argument are also an element of the
second argument, A ⊆ B). Every set is a subset of itself, but it is not a strict
subset of itself.
• The operations union and intersection respectively compute the union (A ∪ B) and
the intersection (A ∩ B) of two sets. The operation without removes all elements
of the second argument from the first argument (A \ B). The function product
computes the Cartesian product of two sets (A × B).
• The function powerSet computes the power set (set of all subsets, ℘(A)) of a given
set A.
Use ZF -expressions, ..-expressions or recursive functions as you see fit. Try to determine
with which core functions you can construct the other functions in this module (for
instance: two sets are equal if they are each other’s subset).
• (top s) gives the top element of stack s if possible, and a runtime error if s is empty.
(topn n s) gives the top n elements of the stack s if possible, and a runtime error if
there are not enough elements.
• (elements s) returns all elements of stack s, from the top to the bottom. (count s)
returns the number of elements on the stack s.
Try to develop a small core of functions you can use to construct the other functions in
this module (for example: define pushes using push).
37
4.27. STACK, PART II CHAPTER 4. LISTS
A (Stack2 elem) is a stack in which elements of type elem are stored. The type of the
implementation (impl) is hidden by the existential quantor (E.).
Stack2 instances
Extend the module TestStdStack2 with two functions that create new stack objects:
listStack :: Stack2 a
charStack :: Stack2 Char
The implementation of listStack has to work with lists of type [a], and the implementation
of charStack has to work with String. So complete the following implementations:
listStack = { stack = [], . . . }
charStack = { stack = "", . . . }
or:
38
CHAPTER 4. LISTS 4.28. SORTED LIST
test new stack2=:{ stack, push } = { stack2 & stack = push new stack }
This is quite a hassle. Add the following access functions to StdStack2.icl, and implement
and export them.
push :: elem (Stack2 elem) -> Stack2 elem
pop :: (Stack2 elem) -> Stack2 elem
top :: (Stack2 elem) -> elem
elements :: (Stack2 elem) -> [elem]
Compare StdStack and StdStack2. Are they the same? Explain your opinion.
• (mergeSortList l1 l2 ) merges l1 and l2 into a new sorted list. The following holds:
count l1 + count l2 = count (mergeSortList l1 l2); if (memberSort x l1 ), then also
(memberSort x (mergeSortList l1 l2)) and (memberSort x (mergeSortList l2 l1)).
39
4.30. CARDS, PART II CHAPTER 4. LISTS
• (updateKey k v l) inserts the key-value pair (k, v) in l if k was not already present
in l, and replaces the previous value associated with key k by v otherwise.
• (removeKey k l) removes the key-value pair associated with k if present, and leaves l
unmodified otherwise.
1. Card deck Write the function carddeck :: [Card] that generates a complete list of
all cards in a deck. Try to write as short a definition as possible.
2. Sorting by value Write function sort_by_value that sorts a deck in ascending
order. First sort by value, then by suit in the order hearts (♥), diamonds (♦),
spades (♠) and clubs (♣). Try to write as short a definition as possible.
3. Sorting by suit Write the function sort_by_suit that sorts a deck of cards in
increasing order. First sort by suit in the same order as above, then by value. Try
to write as short a definition as possible.
40
CHAPTER 4. LISTS 4.32. BOIDS
Roman → Int Write an instance for the class toInt for Roman that satisfies the properties
given above.
Int → Roman Write an instance for the class fromInt for Roman that converts a positive
Int value to the shortest Roman representation.
4.32 Boids
Main module: Boid.icl
Environment: Object IO
Boids were invented in 1986 by Craig Reynolds as an example of artificial life to describe
the movement behaviour of animals in a group, like a flight of birds or a school of fish.
For more background information, see http://www.red3d.com/cwr/boids/.
A simulation consists of a set of individuals named boids. Each boid has a position
and a speed. You can run the simulation both in a space and on a surface. In this exercise,
we use a surface. This means a position is a point (rx, ry), where 0 ≤ rx ≤ 1 and
0 ≤ ry ≤ 1. A speed is a vector (vx, vy). We assume that we have functions pos and vel
that return the position and speed of a boid. Every boid respects three rules that each
determine a share of its next speed, and thus position. A different explanation of these
rules can be found at http://www.vergenet.net/~conrad/boids/pseudocode.html.
The rules are:
1. Boids move to the centre of nearby boids. Let us compute the new partial speed v1
for boid B. Let B1 , . . . , Bn1 be the boids that are within the short distance d1 of
B (experiment with values of d1 ). Then the new partial speed v1 for B is:
Pn1
pos(Bi )
i=1
n1 − pos(B)
v1 = .
100
2. Boids keep (a small) distance from objects, including other boids. Let us compute
the new partial speed v2 for boid B. Let B1 , . . . , Bn2 be the boids that are within
short distance d2 of B (experiment with your own values for d2 ). The new partial
speed v2 for B is:
Pn2
(0, 0) − i=1 (pos(Bi ) − pos(B))
v2 = .
8
3. Boids adjust their speed to the speed of other boids nearby. Let us compute the
new partial speed v3 for B. Let B1 , . . . , Bn3 be the boids within short distance d3
of B (experiment with your own values of d3 ). The new partial speed v3 of B is:
Pn3
vel(Bi )
i=1
n3 − vel (B)
v3 =
8
41
4.33. ALIGNING TEXT CHAPTER 4. LISTS
P
The final new speed of boid B is v = vel (B) + i=1 3vi , and the new position is
pos(B) + v. By applying this computation to each boid B in a set of boids, it is possible
to keep recomputing the new speeds and positions.
Write a function simulate that takes a list of boids, and returns a new list where
the speeds and positions are updated as described above. In the given part of this
exercise a window is opened that allows you to place new boids in the window using
the mouse. These boids have an initial speed of (0, 0). The framework draws boids in a
simple fashion as coloured circles (see figure 4.2). In your implementation, make as much
use of records and/or algebraic data types (to represent boids, speeds and positions),
overloading (adding and subtracting speeds and distances) and list comprehensions as
possible.
42
CHAPTER 4. LISTS 4.34. COMPOSING TEXT
Figure 4.3: The four modes of alignment in action: Left, Centred, Right and Justified.
The program offers a menu where you can choose the active align mode so that you may
conveniently test your program. In addition the program allows you to choose the font
size.
The program is almost finished: the implementation of the alignment function is still
missing. This is the function you need to complete. The type is:
:: Text :== String
:: Line :== String
:: TextWidth :== Int
:: LetterWidth :== Int
The first argument is the requested alignment mode. The second argument gives the
width of each letter (we are using a so-called non-proportional font, i.e. all symbols have
the same width). The third argument is the width of the window in which the text is
drawn. The fourth argument is the text to be aligned.
Note that this program uses the GUI libraries of Clean: Object I/O. Make sure you
have set the environment to Object IO in the Clean IDE after you have made the project.
43
4.34. COMPOSING TEXT CHAPTER 4. LISTS
• The Text instance of the default toString overloaded function transforms a text
block to a String in which the line breaks are implemented using newline characters.
• The overloaded function sizeOf returns the number of columns and rows of its
argument. The String instance treats its argument as a text block of one row and
the Text instance returns the instance’s dimensions.
• The operation (horz av ts) places all text blocks in ts next to each other, and makes
sure their vertical alignment corresponds with av. The width of the resulting text
block is the sum of the widths of the elements in ts. The height is the maximum
height of the elements in ts. The operation (vert hv ts) analogously places all text
blocks vertically, using the horizontal alignment value hv. The width is then the
maximum width of the elements in ts and the height the sum of the heights of the
elements.
• The operation (repeath n c) creates a text block of one row that consists of n c
characters. The operation (repeatv n c) creates a text block of one column that
consists of n c characters.
• The operation (frame t) creates an ascii-art text frame around the block t.
------------
| |
Example: | |
toString (frame (toText (CenterH,10) (BottomV,4) 42)) | |
produces: | 42 |
------------
------------------
Example: | |
toString | Functional|
(frame | Programming|
(toText (RightH,16) | is|
(CenterV,6) | FUN!|
"Functional\nProgramming\nis\nFUN!")) | |
produces: ------------------
44
CHAPTER 4. LISTS 4.35. WORD SLEUTH
E C L L LAZY
P E I A LIST
Y S F Z TYPE
T A N Y ZF
Implement an algorithm that given an m × n matrix of letters and a word list, crosses
out all words as above. The algorithm needs to return the remaining letters from left to
right, top to bottom.
4.37 Change
Main module: Change.icl
Environment: StdEnv
In this exercise you write the program for a change machine. The change machine has an
infinite amount of each kind of coin and bank note. It may however not dispense more
than a specified amount of k coins / bank notes at the same time. Write the function
45
4.38. DOMINOES CHAPTER 4. LISTS
change that, given the value to be exchanged, the available currency, and the maximum
amount of coins / bank notes to be dispensed, computes all possible solutions.
Use the following type synonyms and the type of change:
:: Amount :== Int // a positive number
:: Currency :== Int // a positive number
:: Currencies :== [Currency] // a non-empty list
:: Coin :== Int // a positive number
:: K :== Int // a positive number
:: Change :== [Coin]
4.38 Dominoes
Main module: Domino.icl
Environment: StdEnv
The classical game of dominoes (the so-called double-six variant) consists of 28 rectangular
tiles, called dominoes. Each domino has two halves that each have a number of pips. The
28 dominoes contain all unique combinations of pips, with the number of pips between
zero and six (see also figure 4.4).
There exist variants with other combinations, like double-nine, double-twelve and
even double-eightteen. We call such a collection of dominoes a double-N game (with
N the maximum possible number of pips on one half of a tile). A double-N game has
MAX = (N +2)(N
2
+1)
dominoes. With the tiles of a double N game it is possible to make
46
CHAPTER 4. LISTS 4.39. BAG PACKER
a snake, i.e. a sequence of consecutive dominoes such that the short sides are connected,
and the pips on connecting halves have the same number of pips (so 0 with 0, 1 with
1, etc.). A snake does not contain ‘loops’, which can be played during a real game of
dominoes.
Write the function all_snakes that prints all possible snakes with length MAX of a
double-N game.
Example: for a double-1 game all possibilities of length 3 are:
[0:0] [0:1] [1:1]
[1:1] [1:0] [0:0]
47
4.40. BAG PACKER, PART II CHAPTER 4. LISTS
player wants to fill his inventory in such a way that it has the maximum possible value;
there should be no other combination of items of which the sum of values is greater
(although it may be the same). For our protagonist, write a function that takes the
following arguments:
1. A capacity of the maximal weight.
2. A list of objects that each have a value and weight.
The function returns a collection of items that has the maximum summed value. Design
appropriate data structures for this assignment.
48
CHAPTER 4. LISTS 4.42. BRAILLE
list of preferences for all candidates on the other side, through a numbering of 1 to N ,
where 1 indicates the most preferred candidate and N the least preferred candidate. A
coupling of all suitors and partners is stable when there is no suitor and partner that
would rather be with each other than with their current ‘better half’.
If N is an even number, then there is an algorithm that computes such a stable
coupling. This algorithm was proven correct in 1962 by David Gale and Lloyd Shapely1 .
It is an iterative algorithm that keeps computing the following: find a not yet coupled
suitor and find the most favoured partner to whom the suitor has not proposed yet. The
suitor is rejected if the proposed partner is already coupled with a suitor that is higher
on the proposed partner’s list of favourites. In every other case they accept. This means
that if the proposed partner was already matched up with a suitor (which should then
be lower on her list of favourites), then that suitor is no longer matched. This repeats
until all suitors are coupled.
Implement this algorithm. Do this by implementing the following function:
:: Nr :== Int // 1..N
4.42 Braille
Main module: Braille.icl
Environment: StdEnv
In figure 4.5 the characters from the Dutch Braille writing system are shown (source
muzieum Nijmegen www.muzieum.nl). It was invented by Louis Braille (1809–1852) to
allow those who are blind or visually impaired to read and write texts. Braille is a tactile
script that’s embossed on paper by creating raised dots that can be felt by touch. The
characters are small rectangular blocks called cells that consist of two columns of three
dots. The black dots (•) form the ‘dots’ in the paper and the empty dots (◦) are not
embossed into the paper. The digits 1 to 9 and 0 are the same as the letters a through
j. To prevent ambiguity, numbers are prefixed by the number sequence symbol. The
sentence “The quick brown fox jumps over the lazy dog.” looks as follows in Braille:
◦• ◦• •◦ •◦ •• •◦ ◦• •• •◦ •◦ •◦ •◦ ◦• ••
◦◦ •• •• ◦• •• ◦◦ •◦ ◦◦ ◦◦ •◦ •• ◦• •• ◦•
◦• •◦ ◦◦ ◦◦ •◦ •• ◦◦ ◦◦ •◦ ◦◦ •◦ •◦ ◦• •◦
•• •◦ •• ◦• •◦ •• •• ◦• •◦ •◦ •◦ •◦ ◦• •◦
•◦ ◦• ◦◦ •• ◦◦ ◦◦ •◦ •◦ ◦• •◦ ◦• •• •• ••
◦◦ •◦ •• ◦◦ •• •◦ •◦ •◦ •◦ •• ◦◦ •◦ •◦ ◦◦
•◦
◦•
◦◦
•◦ •◦ •◦ •• •• •◦ •• ◦◦
•◦ ◦◦ ◦• ◦• ◦• ◦• •• ••
•◦ ◦◦ •• •• ◦◦ •◦ ◦◦ ◦•
49
4.42. BRAILLE CHAPTER 4. LISTS
•◦ •◦ •• •• •◦ •• •• •◦ ◦• ◦• •◦ •◦ •• ••
◦◦ •◦ ◦◦ ◦• ◦• •◦ •• •• •◦ •• ◦◦ •◦ ◦◦ ◦•
◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ •◦ •◦ •◦ •◦
a b c d e f g h i j k l m n
•◦ •• •• •◦ ◦• ◦• •◦ •◦ ◦• •• •• •◦
◦• •◦ •• •• •◦ •• ◦◦ •◦ •• ◦◦ ◦• ◦•
•◦ •◦ •◦ •◦ •◦ •◦ •• •• ◦• •• •• ••
o p q r s t u v w x y z
•• •• ◦• •• ◦• •• ◦• •◦ ◦• ◦•
•◦ •• •◦ •◦ ◦• •• •◦ •• •◦ ◦◦
•• •• •• ◦• •◦ ◦• ◦• ◦• •• ••
ç é è ë ä ı̈ ö ü ß &
•◦ •◦ •• •• •◦ •• •• •◦ ◦• ◦•
◦◦ •◦ ◦◦ ◦• ◦• •◦ •• •• •◦ ••
◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦
1 2 3 4 5 6 7 8 9 0
◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦ ◦◦
•◦ •◦ •• •• •◦ •• •• •◦ ◦• ◦•
◦◦ •◦ ◦◦ ◦• ◦• •◦ •• •• •• •◦
, ; : . ? ! () “ ” *
◦◦ ◦◦ ◦◦ ◦◦ ◦◦ •◦
•• ◦◦ •◦ •• •• ••
•◦ •• •• ◦• •• ◦•
+ - × : = /
◦• ◦• ◦◦
◦◦ capital character ◦• all caps ◦◦ undo character
◦• ◦◦ ◦•
◦• ◦◦ ◦•
◦• cursive ◦• change language ◦◦ not used
◦• ◦• ◦◦
◦• ◦◦ ◦◦
◦• rhime ◦• emphasis ◦• repeat
•◦ ◦◦ •◦
◦• ◦◦
◦• number sequence ◦◦ hyphen
•• ••
50
CHAPTER 4. LISTS 4.43. MODERN ENGLISH SPELLING
Reading: write the function readBrailleText :: [(String, String, String)] -> [Char] that
converts Braille texts like the one above to the latin alphabet.
Example:
readBrailleText [(".*.**.*. ***..****. *.*.*..***"
,"..****.* **..*..... *.**.***.*"
,".**..... *.**....*. ..*.*..**."
)] = [’The quick brown’]
51
4.43. MODERN ENGLISH SPELLING CHAPTER 4. LISTS
52
Chapter 5
In earlier chapters we have already used functions that created new functions from
existing functions (like o and flip). Currying allows you to apply a function to less
arguments than its arity (e.g. ((+) 1)). These are all examples of higher order functions.
Higher order functions are essential tools for the functional programming method, because
you can use it to define customizable programming patterns.
5.1 Notations
Main module: HOFNotation.icl
Environment: StdEnv
Below a number of functions are given. Deduce from each the most general type and
explain what the function does.
f1 x y = xy
f2 x y z = x z (y z)
f3 x y = x (x y)
f4 x y z = [v \\ v <- [y .. z] | x v]
f5 x y (z,w) = (x z,y w)
f6 = f5
f7 "-" = -
f7 "+" = +
f7 "*" = *
f7 "/" = /
53
5.3. FUNCTION COMPOSITION CHAPTER 5. HIGHER ORDER FUNCTIONS
3. Deduce the type of the following expressions: uncurry (+), uncurry (-), uncurry (*)
and uncurry (/). What are their semantics?
5.5 Twice
Main module: Twice.icl
Environment: StdEnv
Examine the function twice in StdFunc. What does this function do? Argue the result of
the following Start-rule.
Start = ( inc 0
, twice inc 0
, twice twice inc 0
, twice twice twice inc 0
, twice twice twice twice inc 0
)
If you want to check your answer in the Clean IDE, you have to set the Stack Size to 1M.
54
CHAPTER 5. HIGHER ORDER FUNCTIONS 5.6. ELLIPSE PERIMETER
1 2
s1 = e
4
(2i − 1)(2i − 3) 2
si = si−1 · ·e if i > 1
4i2
p
r12 − r22
e = .
r1
Write a program that calculates the perimeter of an ellipse with radii r1 and r2 (r1 ≥
r2 > 0) up to a desired accuracy. Use until from StdFunc.
55
5.9. WORD FREQUENCY CHAPTER 5. HIGHER ORDER FUNCTIONS
If you compare the two functions, you will realise that they are almost identical.
Write a third, more general function, that can be used by parametrisation to realise the
two existing functions in a much shorter way.
5.10 Origami
Main module: Origami.icl
Environment: StdEnv
56
CHAPTER 5. HIGHER ORDER FUNCTIONS 5.11. ANY AND ALL
Rewrite the following functions from StdEnv using foldl or foldr andλ-abstractions: sum,
prod, flatten, length, reverse, takeWhile and maxList. Rename your new functions sum‘,
prod‘, flatten‘, length‘, reverse‘, takeWhile‘ and maxList‘.
foldl or foldr?
Both the && and the || operator are conditional tests: they inspect first the value of the
first argument, and if the second argument is not needed to determine the result, it is
not evaluated. This is reflected in their types:
:: !Bool Bool -> Bool
The strictness annotation (!) for the first argument indicates that the function will
always evaluate the argument, while the absence of that annotation for the second
argument indicates that it is only evaluated when needed (lazy evaluation).
Predict what will happen when all_l, all_r, any_l and any_r are applied to an infinite
list of booleans. Do this using the following examples:
Start = all_l id [False:repeat True ]
Start = any_l id [True :repeat False]
Start = all_r id [False:repeat True ]
Start = any_r id [True :repeat False]
Lift
Deduce the most general type of the following functions and explain what they do:
lift0 f a = f a
lift1 f g1 a = f (g1 a)
lift2 f g1 g2 a = f (g1 a) (g2 a)
lift3 f g1 g2 g3 a = f (g1 a) (g2 a) (g3 a)
57
5.13. ROMAN NUMERALS II CHAPTER 5. HIGHER ORDER FUNCTIONS
Boolean classes
In StdBool, the following boolean operations are defined:
not :: !Bool -> Bool // Not arg1
(||) infixr 2 :: !Bool Bool -> Bool // Conditional or of arg1 and arg2
(&&) infixr 3 :: !Bool Bool -> Bool // Conditional and of arg1 and arg2
These operations are, contrary to the arithmetic operations +, -, etc., not overloaded. It
often happens that you don’t only want to combine Bool values to a new Bool, but also
predicates (a -> Bool) to a new predicate (a -> Bool). For example: to select all elements
between 3 and 8 you have to write:
Start = filter (\x -> 3 < x && 8 > x) [1 .. 10]
If && would have been overloaded, you could have defined it on predicates, in order to
write:
Start = filter ((<) 3 && (>) 8) [1 .. 10]
To realise this, the following definition module has been made. It expands StdBool with
the desired overloaded operators:
definition module StdBool2
import StdBool
instance ~~ Bool
instance ||| Bool
instance &&& Bool
Write the corresponding implementation module. Use, when possible, the lifti functions.
58
CHAPTER 5. HIGHER ORDER FUNCTIONS 5.14. SCAN AND ITERATE
• The Int operations bit[x]or, bitand, bitnot, << and >> are not overloaded and cannot
be used for Romans.
Use higher order functions in your implementation whenever applicable. In fact, all your
instances should be one-liners.
first using the applicative evaluation strategy and then using the normal order
evaluation strategy.
2. Use the above result to construct the rewriting process of the following Start rule:
Start = scan (*) 1 [3 .. 5]
3. Argue what the result of the following two Start rules would be:
Start = take 5 (iterate (flip (^) 2) 2)
Start = take 10 (iterate (flip (/) 10) 123456)
Implement the function sine which approximates this sequence by taking some
finite part (use take) of the beginning of the infinite list that represents the above
computation.
Taylor sequence for cosine The Taylor sequence for the cosine is defined as:
∞ n
x2 x4 x6 X (−1) · x2n
cosine x = 1 − + − + ... =
2! 4! 6! n=0
(2n)!
Implement the function cosine which approximates this sequence by taking some
finite part (use take) of the beginning of the infinite list that represents the above
computation.
59
5.16. SEQ AND SEQLIST CHAPTER 5. HIGHER ORDER FUNCTIONS
Implement the function pi1 which approximates this sequence by taking some
finite part (use take) of the beginning of the infinite list that represents the above
computation.
Nilakantha sequence for π The Nilakantha sequence to approximate π is defined as:
∞
4 4 4 4 X 4
π−3= − + − + ... = n Q2(n+2)
2 · 3 · 4 4 · 5 · 6 6 · 7 · 8 8 · 9 · 10 n=0 (−1) · i
i=2(n+1)
Implement the function pi2 which approximates this sequence by taking some
finite part (use take) of the beginning of the infinite list that represents the above
computation.
60
CHAPTER 5. HIGHER ORDER FUNCTIONS 5.17. DATABASE QUERIES
• Every album of every group has a unique title. However, there are album titles that
are used by different groups (like the albumUp, which is used by both Peter Gabriel
and R.E.M.).
Below, a number of queries is described. Write for every query a function that answers
it. Use list comprehensions, higher order functions, and standard functions whenever
applicable.
1. Write the function all_groups :: [Song] -> [String], which enumerates all groups
without duplicates and in ascending order according to the < instance on String
values.
2. Write the function all_albums_of :: String [Song] -> [(Int,String)], which enumer-
ates all albums that were published by a given group. List both the year and the
album title without duplicates, and sort in ascending order on the year of release.
3. Write the function all_tracks :: String String [Song] -> [(Int,String,Time)]. It should
list all tracks of a given album and group. Show of each track the track number,
the title and the time. The list has to be sorted in ascending order of the track
number.
4. Write the function time_albums :: [Song] -> [(Time,String,String)], which returns all
albums of all groups. In every result (a, b, c), a is the total playing time, b the
group and c the album. Sort the list in ascending order of the total playing time.
Write the names of the shortest and the longest album down in comments.
5. Write the function total_time :: [Song] -> Time, which returns the total playing time
of all songs. Write the total playing time in the m+ : ss notation of exercise 3.18
in comments.
6. Write the function dutch_metal :: [Song] -> [String], which enumerates all groups
without duplicates where one of the tags equals "metal", and one of the countries
matches "Netherlands".
7. Write the function all_periods :: [Song] -> [String], which enumerates all the years
in ascending order, using short notation. The short notation entails that for every
period of more than one consecutive year only the first and last year are mentioned.
Example: suppose that the albums were from the years 1967, 1968, 1969, 1972,
1973, 1975 and 1978. Then the result of this function is: ["1967-1969", "1972-1973",
"1975", "1978"].
61
5.18. RANDOM NUMBERS CHAPTER 5. HIGHER ORDER FUNCTIONS
This function gets as its argument a RandomSeed with which a pseudo random number
of type Int and a new RandomSeed can be computed. With this new RandomSeed, the next
pseudo random number can be computed, etc.
Since we are working in a pure functional language, Random rs will always return the
same pseudo random number and new RandomSeed.
A more or less random RandomSeed can be retrieved using getNewRandomSeed:
getNewRandomSeed :: *World -> (RandomSeed, *World)
This function uses its *World argument to read the current time, and derives from that
an initial RandomSeed. You can use the *World, that you can get in the Start rule, as the
argument to getNewRandomSeed:
Start :: *World -> ...
Start world
# (rs,world) = getNewRandomSeed world
= ...
On Windows, the Random module uses the StdTime module from Object IO. Therefore, you
have to use the Object IO environment. Linux users can edit Random.icl by following the
comments and avoid the Object IO dependency.
62
CHAPTER 5. HIGHER ORDER FUNCTIONS 5.19. RETURN AND BIND
(id,o) vs. (return,bind) Compare id with return and o with bind. What is the rela-
tionship between these functions? Rewrite bind using o and uncurry, that is, finish
the following definition:
(bind1) infix 0 :: (St s a) (a -> (St s b)) -> St s b
(bind1) f1 f2 = . . . o . . .
63
5.19. RETURN AND BIND CHAPTER 5. HIGHER ORDER FUNCTIONS
64
Chapter 6
In this chapter, we discuss simple console and file I/O. By console I/O we mean text-
based input from and output to the console or terminal window. Using file I/O we can
deal with files in text and binary format. Both forms of I/O are based on the uniqueness
type system of Clean, and use the same abstraction: File and *File.
6.1 Echo
Main module: Echo.icl
Environment: StdEnv
Write a console I/O program that performs the following actions:
1. It opens the console file stdio from the world;
2. reads repeatedly a line of input from stdio and echoes each line through stdio;
3. and exits (cleanly: stdio should be closed in the world) if the input was empty.
6.2 Oh Tannenbaum II
Main module: OTannenbaum2.icl
Environment: StdEnv
Write a console I/O program that repeatedly reads a number as its input and prints the
corresponding Christmas tree as described in exercise 2.10. If you have done exercise 2.10
or 4.34, you may of course use those functions. The program exits when a non-numeric
value is inputted.
Note: the toInt function that converts a String to an Int fails if the string is not
exactly an integer. In particular, this may happen when the console input ends with
a newline. Use the String instance of the overloaded function % to remove the newline
character from the input string.
6.3 Wordplays
Main module: Wordplays.icl
Environment: StdEnv
65
6.4. INTERACTIVE FQL CHAPTER 6. CONSOLE AND FILE I/O
In this exercise, you write a console I/O program that repeatedly reads a command
(as described below) and prints the corresponding output. Use the function words
from exercise 5.8. For each command, reuse the list function that is mentioned if you
implemented it.
66
CHAPTER 6. CONSOLE AND FILE I/O 6.5. MASTERMIND
4. title text: show all tracks of which the .title corresponds to text. Show of every
track the .group, .album, .title and .track number. End with printing the number
of tracks.
5. albums text: show the .albums of the .groups of which the group name corresponds
to text. Show for every group the found albums (the .album and .year). End with
printing the number of albums.
6. tracks text: show all tracks of every .album of which the title corresponds to text.
Show for every album first the .group, .album, .year and total playing time, and
then all tracks (.track, .title and .time) in ascending order of .track.
7. tags{ tag}+ : show all tracks of which the .tags correspond to the tag parameters
of the command (at least one of the parameters should be present in the .tags
field). If for some album all tracks correspond to the request, only the information
of the album has to be printed (.group, .album, .year). If not all tracks correspond
to the request, the information of every track has to be printed separately: .group,
.album, .year, .track and .title.
6.5 Mastermind
Main module: Mastermind.icl
Environment: StdEnv
Mastermind is a code-breaking game for two players: the codemaker and the codebreaker.
• The codemaker thinks of a code that consists of four colours. He can choose from
eight colours: white, silver, green, red, orange, pink, yellow and blue. There are
no restrictions for the code: it may consist of four identical colours, but also four
different colours. In total, there are 84 = 4096 possible combinations.
• The codebreaker tries to guess the code in as few turns as possible. If he needs
more than twelve guesses, he loses. A turn consists of giving a concrete colour
code. The codemaker gives two answers:
– The number of colours that is correct (the right colour on the right position).
– The number of colours (apart from the number of colours that are correct)
that occur in the code (but are not on the right position).
Write a console I/O program that implements the game Mastermind, where the user has
the role of codebreaker. The program is the codemaker. For generating random codes
you can use the pseudo random number generator from exercise 5.18.
Write the implementation in such a way that it abstracts from the constant values
that are mentioned above: it has to work for any code length, for any number of colours
and for any maximum number of tries.
67
6.6. A FILE I/O MODULE CHAPTER 6. CONSOLE AND FILE I/O
use-cases of working with files is to work with the file content entirely as if it were a
single text (String), or a list of lines ([String], or some content onto which you want to
map a function of type (a -> b). In this assignment you create access functions for these
purposes.
Base module
Develop the module SimpleFileIO with the following signature:
definition module SimpleFileIO
The first argument of readFile and writeFile is a path to a file, in the same format as is
needed for the fopen and sfopen functions in StdFile. The last argument of both functions
is a suitable environment on which the file I/O operations are instantiated (like *World).
The result value of type env is, naturally, the modified environment after the operation
has been performed. The function readFile reads in the complete contents txt of the given
file and returns (Just txt ). The file needs to be opened, read and closed. If something
goes wrong, Nothing can be returned. The function writeFile gets as its second argument
the new contents of the given file. If opening the file, writing the new contents to it and
closing the file succeeds, the Bool value should be True; if not, it should be False.
The module can be tested using the first Start rule in TestSimpleFileIO.icl. This
rule copies the module itself to a file with the name TestSimpleFileIO1.icl.
Line by line
Add read and write functions to this module that consider the contents as a list of lines
(ended with ’\n’ except for the last line):
readLines :: String *env -> (Maybe [String],*env) | FileSystem env
writeLines :: String [String] *env -> (Bool, *env) | FileSystem env
Test these functions using the second Start rule in TestSimpleFileIO.icl. This rule
stores the lines in TestSimpleFileIO1.icl, in reversed order, in TestSimpleFileIO2.icl.
An overloaded interface
Add an overloaded function mapFile to SimpleFileIO:
mapFile :: String String (a -> b) *env -> (Bool,*env) | FileSystem env
& fromString a & toString b
The first argument to mapFile is a path to a file of which the contents will be read. The
second argument is a path to a file to which we will write. The third argument, a
function of type a -> b, has to be applied to the contents of the first file, after conversion
from string has been applied, and the result of which is written to the second file, after
conversion to string has been applied.
68
CHAPTER 6. CONSOLE AND FILE I/O 6.7. MONADIC I/O
You can test the new function with the third Start rule in TestSimpleFileIO.icl. It
converts all characters from TestSimpleFileIO2.icl to upper-case and writes these to
TestSimpleFileIO3.icl.
It gets as its first argument the name of a text file that needs to be opened, using
the FileSystem environment env. If successful, it counts the number of words in that
file. Use the simplistic function words from exercise 5.8 for counting the words. Use the
SimpleFileIO module from exercise 6.6 for reading the file. If it fails, it returns zero, and
the environment.
69
6.9. WORD FREQUENCY II CHAPTER 6. CONSOLE AND FILE I/O
It gets as its argument the name of a text file that needs to be openend, using the
FileSystem environment env. If successful, it returns the frequency table of the words in
that text file, as described in exercise 5.9. Use the SimpleFileIO module from exercise 6.6
for reading the file. If you have completed the optional exercise 5.9.1, you can use the
showFrequencylist2 function for the visualisation. If it fails, it returns the empty list, and
the environment.
6.10 Hangman
Main module: Hangman.icl
Environment: ObjectIO
The game Hangman is a word guessing game for two players. One player thinks of a
word consisting of at least two letters. The other player has to guess the word in as few
turns as possible. The length of the word is known to the guesser. In every turn, the
guesser may do either of the following:
• Say a letter. The challenger shows all positions of that letter in the word. If the
letter does not occur in the word, the guesser gets a penalty point.
• Say a word. The challenger says if that is the word he had in mind. If this is
correct, the guesser wins. If it is not correct, the guesser gets a penalty point.
After every turn in which the guesser has gotten a penalty point, a part of a gallows is
drawn. This can be done using ASCII-art. For example:
------ ------ ------ ------ ------
| \ | | \ | | \ | | \ | | \ |
| \| \| o \| o \| o \|
| | | | /O\ | /O\ |
| | | | | / \ |
| | | | | |
-------- -------- -------- -------- -------- -------- --------
70
CHAPTER 6. CONSOLE AND FILE I/O 6.11. ONE-TIME PAD ENCRYPTION
text files using the one-time pad method1 . For this we need random streams, so you
will have to work with random numbers (see exercise 5.18). But first some background
information about the one-time pad method.
OTP is an encryption method which has, theoretically, perfect security and is thus
unbreakable. The idea is that you can manipulate a message by XOR-ing it with a truly
random number stream, such that it only contains ambiguous data. To decrypt the data,
you need an exact copy of the random number stream used for encryption. In theory,
this method is unbreakable. In real, there are several problems:
• Generating true randomness is not trivial. Most random number generators found
in programming language are not truly random.
• Every random stream may be used only once, and has to be destroyed afterwards.
Destroying data on a common computer is difficult.
• The transmitter and receiver must have an exact copy of the random stream.
Therefore, the system is vulnerable to damage, theft and copying.
Despite these drawbacks, the OTP method has been used by for example the KGB (the
main security agency of the former Soviet Union). They used truly random streams
printed in a very small font, so that it could be hidden relatively easily (see figure 6.1).
Figure 6.1: Truly random stream sources for OTP encryption, used in espi-
onage. Source: http://www.ranum.com/security/computer security/papers/-
otp-faq/ (left image); http://home.egge.net/~savory/chiffre9.htm (right image).
In this exercise, you implement the OTP method for encrypting files. Since we don’t
have truly random streams, you work with the pseudo random number generator from
exercise 5.18. Choose an integer R for your encryption method. Keep this value secret.
Use R as the RandomSeed to generate a sequence of pseudo random numbers r0 , r1 , . . . .
71
6.11. ONE-TIME PAD ENCRYPTION CHAPTER 6. CONSOLE AND FILE I/O
This case distinction ensures that non-printable ascii characters (0 ≤ ai < 32) are
unchanged, and that other ascii characters (32 ≤ ai < 128) are encrypted. When
encrypting, ⊕ is addition (+). When decrypting, ⊕ is subtraction (−). The addition of
(128 − 32) is superfluous when encrypting, but it guarantees that intermediate results
are positive values in case of decrypting.
Write a console I/O program that accepts the input otp A B, where A and B are
paths to text files. It encrypts the contents of A and writes the result to B. The input
pto A B does the reverse: it decrypts A and writes the result to B.
Test your program on a representative input file A, that you encrypt and then decrypt.
Verify that the decrypted file is identical to A.
You can use the module SimpleFileIO from exercise 6.6 to read and write the files.
72
Chapter 7
Recursive Types
Up to this point, disregarding lists, we have not yet made use of recursive data structures
in the exercises. Recursive data structures can be created with algebraic types and
records. Lists are a special case. Recursive data structures are also known as tree
structures, because they have a starting point, known as the root, and a branching
structure. The principles you employed to work with lists are also applicable to tree
structures. This means that their size is unbounded, and that calculations can be stored
for potential future computation.
Tree metrics Implement the functions nodes, leaves and depth: nodes counts the number
of Nodes, leaves counts the number of Leafs, and depth is the maximum number of
Nodes that you can encounter by traversing the tree from its root to one of its leaves.
As such:
73
7.2. DISPLAYING TREES CHAPTER 7. RECURSIVE TYPES
Number of nodes versus number of leaves versus depth The metrics of binary
trees are related to each other. Show that the following metrics hold for all finite
trees t by using structural induction on t.
1. nodes t = leaves t − 1.
2. leaves t ≤ 2depth t .
2. a function tree2D utilizing the module TextCompose from exercise 4.34 to produce a
more traditional representation of the tree structure.
Choose the instance that you want to implement, test or use by commenting away the
other instance. In this exercise you can use the functionality to assess whether you have
drawn the trees in exercise 7.1 correctly.
74
CHAPTER 7. RECURSIVE TYPES 7.3. BINARY SEARCH TREES
(Node 4 This output shows you that the distances of nodes to the
(Node 1 left column depend linearly on their depths in the tree. The
Leaf root node is placed on the left column, its child nodes are
Leaf indented one level, the children’s child nodes are indented
) two levels, etc. The parenthesis structure further indicates
(Node 5 which elements belong together.
(Node 2
Leaf
Leaf
)
Leaf
)
)
Implement the function indentTree that prints a binary tree structure as described
above. Test your function with trees t0 up to and including t7 from exercise 7.1.
7.2.2 Printing in 2D
The output generated by indentTree is not always clearly readable, because nodes at the
same depth can be displayed far from each other. Sometimes it is clearer to display
these nodes side by side. The function tree2D receives a binary tree structure, such as t7
from exercise 7.1, and prints it as follows:
4
This output shows you that nodes of the same depth are
|
displayed side by side. The two subtrees of a node are
--------------
| | displayed side by side as two text blocks. This suggests that
1 5 the TextCompose module developed in exercise 4.34 can be
| | applied here.
----- -------- A challenging aspect of displaying tree structures in this
| | | | way is that the size of the left subtree is not in general
Leaf Leaf 2 Leaf equal to that of the right subtree. This has consequences
| for determining the position of the corresponding node.
-----
| |
Leaf Leaf
Implement your function tree2D that prints a binary tree structure as described above.
Test your function with trees t0 up to and including t7 from exercise 7.1.
insert Study the function insertTree (lecture notes, section 3.6.2, p. 73). Draw trees
z0 . . . z7 :
75
7.4. TRAVERSING TREES CHAPTER 7. RECURSIVE TYPES
z0 = Leaf
z1 = insertTree 50 z0
z2 = insertTree 10 z1
z3 = insertTree 75 z2
z4 = insertTree 80 z3
z5 = insertTree 77 z4
z6 = insertTree 10 z5
z7 = insertTree 75 z6
If you performed exercise 7.2, check your answers using your implementation of
the toString instance for binary trees.
delete Study the function deleteTree (lecture notes, section 3.6.4, p. 75). Draw tree z8 :
z8 = deleteTree 50 z7
If you performed exercise 7.2, check your answer using your implementation of the
toString instance for binary trees.
ordered A binary tree is ordered if for each node with element x holds that all elements
in the left subtree are smaller than or equal to x and that all elements in the right
subtree are greater than x. The ordering relation of elements is determined by
the <-instance of the element’s type. Implement the function is_ordered that only
results in True if a tree is ordered. As such (with trees t0 . . . t7 from exercise 7.1):
is ordered t0 = True is ordered t4 = False
is ordered t1 = True is ordered t5 = True
is ordered t2 = True is ordered t6 = False
is ordered t3 = True is ordered t7 = False
balanced A binary tree is balanced if for each node in the tree holds that the difference
in depth (see exercise 7.1) of its left and right subtrees is at most 1. Implement the
function is_balanced that only results in True if a tree is balanced. As such (with
trees t0 . . . t7 from exercise 7.1):
An example of a binary search tree is (Nodes and Leafs are not shown):
76
CHAPTER 7. RECURSIVE TYPES 7.5. MAP AND FOLD OVER TREES
10
R
@
6 14
U @
A R
2 7 12 17
U
A AU
4 11 13
U
A
5
Search trees can be converted to lists in different ways. Implement the three functions
listAscending, listDescending, and listToLeaves, all of type (Tree a) -> [a]. The require-
ments of the functions are:
const :: a b -> a
const x _ = x
For each of the functions below, give the most general type and describe what the
functions calculate:
77
7.6. GENERALIZED TREES CHAPTER 7. RECURSIVE TYPES
f1 = foldbtree (+)
f2 = foldbtree (+) o (mapbtree (const 1))
f3 = foldbtree (\x y -> 1 + max x y) o (mapbtree (const 0))
f4 = foldbtree (++) o (mapbtree (\x -> [x]))
They are more general than the binary trees we have seen so far, because the type of
elements in the Nodes can differ from the type of elements in the Leafs. Additionally, a
node can have 0 or more subtrees. Implement the following functions for these trees:
:: Either a b = This a | That b
78
CHAPTER 7. RECURSIVE TYPES 7.7. DISPLAYING GENERALIZED TREES
1. indentTree that prints the tree by systematically indenting nodes with the same
depth with the same level of indentation; and
2. tree2D that prints the tree by displaying nodes with the same depth side by side,
utilizing TextCompose from exercise 4.34.
As an example, we use the following, somewhat strangely formed, tree structure:
:: Void = Void
instance toString Void where toString _ = "."
This generalized tree structure has subtrees that each have one fewer subtree. At the
bottom of the tree you find leafs that do not contain any further useful information.
That is represented by the Void type, using . to print it.
7.7.2 Printing in 2D
4 Develop, in analogous manner as in
| exercise 7.2.2, an algorithm that does
------------------------------------ the same for generalized trees. An ex-
| | | | ample output of pyramid 4 is shown on
3 3 3 3 the left. In contrast with the indenta-
| | | | tion algorithm, this results in a better
-------- -------- -------- --------
distributed output.
| | | | | | | | | | | |
2 2 2 2 2 2 2 2 2 2 2 2
| | | | | | | | | | | |
-- -- -- -- -- -- -- -- -- -- -- --
| | | | | | ||||||||||||||||| |
. . . . . . ................. .
79
7.8. FAMILY TREES CHAPTER 7. RECURSIVE TYPES
For convenience we assume the following, very conservative situation: people get children
only after they are married, i.e. when they form a couple. People do not remarry. A
person p is born as a (Single p) and after marrying with a person q goes through life as
the couple (Couple p q). The person q is related by marriage and does not belong to the
offspring of someone.
Implement the following functions for the family tree:
okFamilyTree :: FamilyTree -> Bool
rootAncestor :: FamilyTree -> Person
inFamilyTree :: Person FamilyTree -> Bool
marry :: Person Person FamilyTree -> FamilyTree
addChild :: Person Couple FamilyTree -> FamilyTree
children :: Person FamilyTree -> [Person]
offspring :: Person FamilyTree -> [Person]
80
CHAPTER 7. RECURSIVE TYPES 7.9. DISPLAYING FAMILY TREES
import StdClass
:: AVLTree a
mkAVLLeaf :: AVLTree a
mkAVLNode :: a -> AVLTree a
isMemberAVLTree :: a (AVLTree a) -> Bool | Eq, Ord a
insertAVLTree :: a (AVLTree a) -> AVLTree a | Eq, Ord a
deleteAVLTree :: a (AVLTree a) -> AVLTree a | Eq, Ord a
isAVLTree :: (AVLTree a) -> Bool | Eq, Ord a
The functions mkAVL(Leaf/Node) create an empty AVL tree / node with a given value
and empty subtrees; the function isMemberAVLTree tests whether a given value occurs
in the AVL tree; insertAVLTree adds an element to the AVL tree; deleteAVLTree removes
an element from the AVL tree; isAVLTree is a predicate testing whether the given AVL
tree is constructed correctly. This last function would not usually be incorporated in
the abstract signature of correctly constructed AVL trees, but it is useful during the
development process of the functions to test whether no errors have occurred.
Optional
If you have performed exercise 7.2, which can print binary trees, then it can be used
as a convenient tool to be able to see whether your AVL trees have been constructed
correctly.
81
7.11. SORTED FILES AND TREES CHAPTER 7. RECURSIVE TYPES
82
CHAPTER 7. RECURSIVE TYPES 7.13. HUFFMAN CODING
Another consequence of using codes of different lengths, is that the code of one
character cannot be the prefix of the code of another character. This requirement is
called prefix code (sometimes: prefix-free code). The algorithm that was developed by
Huffman to generate a prefix code is explained on https://en.wikipedia.org/wiki/
Huffman_coding. Examine this algorithm.
In this exercise, you write a console I/O program that implements the commands
below. Use the SimpleFileIO module from exercise 6.6 to deal with reading and writing
files.
83
7.14. PROPOSITIONAL LOGIC CHAPTER 7. RECURSIVE TYPES
huffmanXX.txt, compresses the text file f ile.txt using that code and writes the
result to f ile.huf.
Huffman code table × compressed file → text file
The command decode huffmanXX.txt f ile.huf reads the Huffman code from
huffmanXX.txt, decompresses the compressed file f ile.huf using that code and
writes the result to f ile.txt.
A boolean value can be assigned to every term t. This is done utilizing an interpretation
[[t]]. This interpretation is also defined inductively:
84
CHAPTER 7. RECURSIVE TYPES 7.14. PROPOSITIONAL LOGIC
7.14.2 Variables
We now add variables to the inductive definition of terms. The following rule is added:
4. the variables vi (i ∈ {1, 2, 3, . . .}) are terms.
An example of a term with variables is:
not(or (and (v1 , v2 ), or (v2 , and (v2 , true))))
To interpret a term t with variables you need extra information. A valuation of variables
is an image that assigns a single value to each variable. For the above example,
val = {(v1 , true), (v2 , false)} is a possible valuation of the variables {v1 , v2 }. We extend
the interpretation [[t]] with this valuation: [[t]]val and the following rule:
D. The value of vi , given a valuation val = {. . . (vi , bi ) . . .} is bi . This is written as
val (vi ). As such: [[vi ]]val = val (vi ).
In the rules A up to and including C, the valuation is passed through unaltered.
Representation Adapt PropL such that variables can be represented.
Printing Adapt the PropL instance of the toString type class such that variables can
also be converted to Strings.
Evaluation Implement the function eval2 that extends eval with a valuation to be able
to interpret variables. Determine a suitable representation for valuations, and call
this type Valuation.
Filtering variables Implement a function vars that receives a term t of type PropL
and yields a list of all variables in t. The list should not contain duplicates. The
variables of above example are v1 and v2 .
Valuations Implement a function vals that yields all possible valuations for variables
in a list of variables without duplicates.
When is it true? Finally, use the above functions to implement a function truths that
receives a term t of type PropL, and yields all valuations that make t true. For
the term defined above, the following valuations should be yielded: {(v1 , true),
(v2 , false)} and {(v1 , false), (v2 , false)}.
85
7.15. 3-VALUED LOGIC CHAPTER 7. RECURSIVE TYPES
As such, an expression can be a number n (represented as (NR n)), a variable with the
name x (represented as (VAR x)), an arithmetical operation (e1 op e2 ) (represented as (OP
e1 op e2), or a declaration (let n = e1 in e2 ) (represented as (LET n e1 e2)). The above
expressions Ei will then be represented as Expr values Ei:
86
CHAPTER 7. RECURSIVE TYPES 7.16. REFACTORING EXPRESSIONS
E1 = OP (LET "x" (OP (NR 42) MIN (NR 3)) (OP (VAR "x") DIV (NR 0)))
PLUS
(LET "y" (NR 6) (OP (VAR "y") MUL (VAR "y")))
E2 = LET "x" (NR 42) (OP (VAR "x") PLUS (LET "x" (NR 58) (VAR "x")))
E3 = LET "x" (NR 1) (LET "y" (NR 2) (LET "x" (NR 3) (NR 4)))
E4 = LET "x" (NR 1) (OP (VAR "x") PLUS (VAR "y"))
E5 = OP (LET "x" (NR 1) (VAR "x")) MUL (VAR "x")
This instance should display Expr values as shown above. The result of Start = map toString
[E1,E2,E3,E4,E5] is the list with subsequent strings E1 , E2 , E3 , E4 , E5 .
Note that no parenthesis may be placed around arguments of calculations consisting
only of a number or a variable.
7.16.4 Evaluator
The value of an expression is calculated by an evaluator. The value of a number is the
number itself. The value of the use of a variable is its definition. That is only possible
for defined variables, and as such expressions E4 and E5 have no value. The value of
an arithmetical operation is the arithmetical operation on the values of its arguments.
We use calculations on integers. Zero division is not allowed, and as such expression E1
has no value. The value of a variable definition is the use of its new definition in the
remainder of the expression. Note that this means the value of E2 is 10, and not 84 or
116.
The evaluation of expressions can succeed – in which case it yields an integer value –
or fail (due to use of an undefined variable or division by zero) – and yield an undefined
value. This is represented as follows:
87
7.17. A λ-REDUCER CHAPTER 7. RECURSIVE TYPES
Implement the function eval :: Expr -> Val calculating the value n of an expression
and, if it exists, yielding that value as (Result n). If the expression is not a valid
calculation, Undef should be yielded. The result of Start = map eval [E1,E2,E3,E4,E5] is
[Undef,Result 100,Result 4,Undef,Undef].
7.17 A λ-reducer
Main module: Lambda.icl
Environment: StdEnv
The λ-calculus is a strongly simplified form of a functional programming language. Terms
in the λ-calculus conform to the following syntax:
term ::= con | var | (term term) | (λ var . term)
A term is a constant (con), a variable (var ), an application of two terms t1 and t2 (t1
t2 ) or a λ-abstraction (λxi .t), in which the variable xi may or may not occur in t. A
variable that is introduced by a λ-abstraction is called bound. The occurrences of these
variables in t are not free. Variables that are not introduced by a λ-abstraction are
called free variables. Examples of λ-terms are:
T0 ≡ 42
T1 ≡ x0
T2 ≡ (λx0 .x0 )
T3 ≡ ((λx0 .x0 ) 42)
T4 ≡ ((λx0 .(x0 x0 ))(λx1 .(x1 x1 )))
T5 ≡ (λx0 .(λx1 .x0 ))
T6 ≡ ((λx0 .(λx1 .x0 )) 42) ((λx0 .(x0 x0 ))(λx1 .(x1 x1 )))
λ-terms can be represented in Clean utilizing an algebraic type:
:: Term = C Value // constant v (C v)
| X Index // variable xi (X i)
| (@.) infixl 7 Term Term // application t1 t2 (t1 @. t2 )
| \. Index Term // abstraction λxi .t (\.i t)
:: Value :== Int // arbitrary integer value
:: Index :== Int // index i (usually i ≥ 0)
We limit ourself to integer constants v (C v). We denote the variable xi with index i as
(X i). The application of two terms t1 and t2 is (t1 @. t2 ). The λ-abstraction λxi .t is
denoted as (\.i t). The above terms Ti are represented by the following Clean terms ti :
t0 = (C 42)
t1 = (X 0)
t2 = (\.0 (X 0))
t3 = (\.0 (X 0)) @. (C 42)
t4 = (\.0 ((X 0) @. (X 0))) @. (\.1 ((X 1) @. (X 1)))
t5 = (\.0 (\.1 (X 0)))
t6 = (\.0 (\.1 (X 0))) @. (C 42) @. t4
88
CHAPTER 7. RECURSIVE TYPES 7.17. A λ-REDUCER
7.17.1 Printing
Implement an instance of the toString type class for the type Term. This instance converts
Term values to a String. Avoid displaying unnecessary parentheses around constants and
variables. Display (X i) as "x_i". The Clean terms ti can be converted to Strings as follows
(your solutions are allowed to differ somewhat):
toString t0 = "42"
toString t1 = "x_0"
toString t2 = "(\x_0 . x_0)"
toString t3 = "(\x_0 . x_0) 42"
toString t4 = "(\x_0 . (x_0 x_0)) ((\x_1 . (x_1 x_1)))"
toString t5 = "(\x_0 . (\x_1 . x_0))"
toString t6 = "(\x_0 . (\x_1 . x_0)) 42 ((\x_0 . (x_0 x_0)) ((\x_1 . (x_1 x_1))))"
Just like in Clean every (sub)expression of a λ-term of the form ((λx.t1 ) t2 ) is a redex
(red ucable ex pression). A λ-term without redex is in normal form.
Rewriting a redex ((λx.t1 ) t2 ) proceeds, just like in Clean, by substituting all free
occurrences of the variable x in t1 by t2 . This is known as uniform substitution. We
denote this as t1 <: (x, t2 ). A variable x occurs free in t1 if it is not bound by a
λ-abstraction within t1 . For example, in the term ((λx0 .x0 )x0 ) only the last occurrence
of x0 is free, because the first occurrence is bound by the λ-abstraction. So, the result
of ((λx0 .x0 )x0 ) <: (x0 , 42) is ((λx0 .x0 )42).
7.17.3 Variables
Implement the function vars :: Term -> [Index] that yields all free and bound variables
without duplicates in a Clean representation of a λ-term.
89
7.17. A λ-REDUCER CHAPTER 7. RECURSIVE TYPES
7.17.5 Substitution
Implement an operator (<:) infixl 6 :: Term (Index,Term) -> Term that performs uniform
substitution as described above.
7.17.6 Reduction
Implement the function beta_reduce :: Term Term -> Term that takes the Clean representa-
tions of the λ-terms (λx.t1 ) and t2 as arguments, and yields the Clean representation of
term t such that (λx.t1 )t2 −→β t. If the first argument is not a λ-abstraction, beta_reduce
should generate an error message.
A λ-term t is reduced by repeatedly applying the −→β rule until the resulting term is
in normal form. In the lecture, two reduction strategies were covered: normal order
and applicative order. Both strategies choose the left-most, outermost redex in t (the
left-most redex in the Term representation, that is highest in the data structure). Normal
order reduction rewrites the redex, while applicate order reduction first rewrites the
argument until it is in normal form, and only then rewrites the resulting redex.
7.17.7 Strategy
Implement the functions normal_order :: Term -> Term and applicative_order :: Term -> Term
that search for the left-most, outermost redex in a term t and rewrite it according to
the normal order and applicate order evaluation strategies. Rewriting of terms itself is
handled by the beta_reduce function.
Example:
t = (\.0 (\.1 (X 0))) @. ((\.0 (X 0)) @. (C 42)) @. (C 50)
normal_order t = (\.1 ((\.0 (X 0)) @. (C 42))) @. (C 50)
applicative_order t = (\.0 (\.1 (X 0))) @. (C 42) @. (C 50)
90
CHAPTER 7. RECURSIVE TYPES
7.18. MAP AND TYPE CONSTRUCTOR CLASSES
Example:
u1 = (\.0 (\.1 (X 0))) @. ((\.0 (X 0)) @. (C 42)) @. (C 50)
u2 = applicative_order u1 = (\.0 (\.1 (X 0))) @. (C 42) @. (C 50)
u3 = applicative_order u2 = (\.1 (C 42)) @. (C 50)
u4 = applicative_order u3 = C 42
Develop a type constructor class with the name Map (map is already in use) inside the
Map.dcl and Map.icl modules such that the functions map, mapMaybe and mapTree are the
implementations of instances of the type constructor class Map of the respective type
constructors [], Maybe, and Tree. The main module Mapping.icl contains a call for each
of these instances.
91
7.20. CONNECT FOUR CHAPTER 7. RECURSIVE TYPES
In exercise 7.16.4 you implemented an evaluator for let expressions that are represented
according to the following recursive data structure:
:: Expr = NR Int
| VAR Name
| OP Expr Operator Expr
| LET Name Expr Expr
:: Name :== String
:: Operator = PLUS | MIN | MUL | DIV
For completeness the definitions of the MonadFail type constructor class are added in the
RefactorXX.icl module:
class fail c :: c a
class return c :: a -> c a
class (>>=) infix 0 c :: (c a) (a -> c b) -> c b
class Monad c | return, >>= c
class MonadFail c | Monad, fail c
Test your monadic evaluator for the list instances of the MonadFail type constructor class.
Test your monadic evaluator for the Val instances of the MonadFail type constructor class.
92
CHAPTER 7. RECURSIVE TYPES 7.21. NIM
Write a program that implements connect four. The user is able to choose the
dimensions of the playing field, and can choose whether they can make the first move;
the program should implement an opponent. Implement the logic of the opponent
utilizing a game tree: i.e., develop a GameState data type and moves and worth functions.
7.21 Nim
Main module: Nim.icl
Environment: StdEnv
}
In the game nim two players play against each other. They } }
begin with a series of stacks that each consist of an arbit-
} }
rary number of objects (e.g., 6-4-1-2-7 in the figure on the
right).The players alternate taking at least one object from }} }
a single stack (i.e., they can take at most the number of }} }
objects on that stack). The player that removes the last }} }}
object is the winner. }}}}}
A B C D E
Write a program that implements nim. The user can choose whether they make the
first move; the program should implement an opponent. Implement the logic of the
opponent utilizing a game tree: i.e., develop a GameState data type and moves and worth
functions.
7.22 Othello
Main module: Othello.icl
Environment: StdEnv
At the start of the game, the colours are assigned to the players. The players alternate
placing one disc of their own colour on the board. They should ensure that in doing this
they enclose at least one row (horizontal, vertical or diagonal) of their opponent: i.e.,
on both sides of the row of stones of the opponent there now is at least one stone of the
player making the move. In the starting configuration, the player with the black discs
can enclose the white discs by playing on the fields marked by an A, B, C or D.
The result is that all newly enclosed discs of the opponent change colour, and now
belong to the player that made the move. Placing a disc can cause multiple rows
(horizontal, vertical, diagonal) to become enclosed, and all these discs are conquered.
If a player cannot place a disc with which at least one disc is conquered, their turn is
93
7.23. BLOKUS CHAPTER 7. RECURSIVE TYPES
over and the opponent may play. The game is finished once the board consists of discs
that are all the same colour, or if the board is full. Once the game is finished, the player
with the most discs of their colour on the board is the winner.
Write a program that implements Othello. The user can choose whether they make
the first move; the program should implement an opponent. The user always plays with
black discs. Implement the logic of the opponent utilizing a game tree: i.e., develop a
GameState data type and moves and worth functions.
7.23 Blokus
Main module: Blokus.icl
Environment: StdEnv
Blokus is a strategy game that can be played by two and four players. In this exercise we
will assume there are two players. See http://www.blokus.com for more information
about blokus.
The blokus game consists of a playing field of 14 × 14 grid cells, of which two are
marked, and each player has an identical collection of 21 blocks in their own colour (see
Figure 7.1).
•
• •
• • • • •
• • • • • • • • • •• •
• • • •• • •• • •• •• •
• • •
• • • •• •• • • ••
• •• • • • • • • ••
• • • • • • • • • •• • • • •
• • •
• • • • • • •• •
• • •
Figure 7.1: The blokus playing field and the 21 blokus playing blocks
The players alternate place one of the playing blocks on the field. The first block
of each player should be placed on the marked cell on the board. The next block of
a certain colour should touch a block that has already been placed on the field. The
blocks may only teach on the corners, and not on the faces. The block to be placed may
touch faces of blocks of another colour. If a player cannot place a block, their turn is
over. The game ends if no players can place a block, or if all blocks have been placed.
The scoring is determined by the blocks that could not be placed on the field. Each
block counts as −1. If a player placed all their blocks on the board, they earn +15
points. If a player placed the smallest block last, they earn +5 points. The player with
the highest score wins.
94
CHAPTER 7. RECURSIVE TYPES 7.24. ABALONE
Write a program that implements blokus. The user can choose whether they make
the first move; the program should implement an opponent. Implement the logic of the
opponent utilizing a game tree: i.e., develop a GameState data type and moves and worth
functions.
7.24 Abalone
Main module: Abalone.icl
Environment: StdEnv
Abalone (see https://en.wikipedia.org/wiki/Abalone_(board_game)) is a strategic
board game for two players. The six-sided board consists of 61 holes in which white and
black marbles can be placed. Figure 7.2a.3 shows the board and the standard starting
configuration.
i
- i
i
- i
i
- i
i i
i i
i i
The player with the black marbles starts. The players alternate their turns. A turn
consists of moving one, two or three marbles of the same colour connected in a single
direction with one step in the same direction. I.e., this can be forwards or backwards (in
the length direction) or sidewards. See Figure 7.2b. Marbles of one colour may push
marbles of the other colour, but not of the same colour (otherwise you would move too
many of your own marbles). In this case, the pushing marbles should be in the majority:
to push one marble of your opponent, you need two or three of your own marbles, and
pushing two marbles of your opponent is only possible with three of your own marbles.
As a consequence, you can never push marbles of your opponent when moving sidewards.
The goal of the game is to be the first to push six marbles of your opponent off the
board.
Write a program that implements Abalone. The user can choose whether they make
the first move; the program should implement an opponent. Implement the logic of the
opponent utilizing a game tree: i.e., develop a GameState data type and moves and worth
functions.
3 Source: http://nl.wikipedia.org/wiki/Bestand:Abalone_standard.svg
95
7.24. ABALONE CHAPTER 7. RECURSIVE TYPES
96
Chapter 8
Correctness proofs
In these exercises you prove a number of propositions correct. These propositions use
function definitions. Every alternative in a function definition is numbered. In the
proofs, use these numbers to indicate which alternative you applied and underline that
part of the expression which you applied it to. If you use the equivalence from right to
left, indicate that using a ⇐. Do not skip any steps.
First, complete the proof on paper. You will certainly need to provide one or two
proofs at the exam, so doing proofs on paper makes for good practice.
When you completed the proof, you can enter it as ascii text or with a word processor
of your preference. In the latter case, you must upload your solution as a pdf document.
For example, let’s say you need to prove for all finite lists xs that: xs ++ [] = xs by the
given definition of ++:
(++) :: [a] [a] -> [a]
(++) [] ys = ys (1)
(++) [x : xs] ys = [x : xs ++ ys] (2)
Base case:
assume xs = []
Prove: xs ++ [] = xs
Proof:
(assumption) xs ++ [] = xs (assumption)
** **
(1) <=> [] ++ [] = []
********
<=> [] = [] .
Induction case:
assume property holds for certain xs:
xs ++ [] = xs (IH)
Prove: [x : xs] ++ [] = [x : xs] for all x
97
8.1. MAP AND O CHAPTER 8. CORRECTNESS PROOFS
Proof:
(2) [x : xs] ++ [] = [x : xs]
**************
(IH) <=> [x : xs ++ []] = [x : xs]
********
<=> [x : xs] = [x : xs] .
(f o g) x = f (g x) (3)
Prove the following proposition for all finite lists xs and functions f and g:
Prove the following proposition for all finite, non-empty lists xs:
98
CHAPTER 8. CORRECTNESS PROOFS 8.3. PEANO ARITHMETIC
In other words, 0 is a natural number (Zero), and if x is a natural number, then the
successor of x (Suc x) is also a natural number. The function that shows the relation
between Nat and Int is the following:
( ## ) :: Nat -> Int
( ## ) Zero =0 (1)
( ## ) (Suc n) = 1 + ##n (2)
99
8.5. LISTS AND TREES CHAPTER 8. CORRECTNESS PROOFS
Prove the following proposition for all finite lists as and bs and functions f (carefully
consider on what list you apply the induction!).
map f (as ++ bs) = (map f as) ++ (map f bs) (8.4.1)
Prove the following proposition using complete induction on the length of list xs. Assume
that xs is a finite list:
flatten (map (map f) xs) = map f (flatten xs)
Make use of property 8.4.1. Note: if you haven’t proven 8.4.1 you may assume it holds.
Explain what the functinos foldbtree and tips do. Prove the following proposition for
any function f and any finite binary tree t:
map f (tips t) = tips (mapbtree f t)
100
CHAPTER 8. CORRECTNESS PROOFS 8.6. SUBS AND MAP
Explain what subs computes and give the result of the expression (subs [’abcd’]). Prove
the following proposition for all functions f and finite lists xs:
subs (map f xs) = map (map f ) (subs xs).
You may use the following lemmas that hold for all functions f, g and finite lists xs and
ys:
101
8.6. SUBS AND MAP CHAPTER 8. CORRECTNESS PROOFS
102
Chapter 9
Dynamics
Dynamics enable you to wrap calculations and pass them on. During wrapping, not only
the calculation is stored in some way, but a representation of its type is also stored, i.e.:
a dynamic value is an encapsulation of an object together with its type. An encapsulated
calculation can be passed to other functions as per usual, but it can also be written to
a file to be read at a later point by the same or another application. The application
unpacking such a dynamic should indicate which type is expected. Only if the expected
type can be made to conform with the actual type the application can use the concrete
content. These checks take place during the execution of the program.
9.1 Notations
Main module: NotationDynamics.icl
Environment: Experimental
A number of functions are given below. Derive the most general type of each of the
functions. Explain what each function does.
f1 (x :: Int) y =x+y
f5 = f4 f3
f6 x (y :: a^) = x == y
f6 _ _ = False
103
9.2. GUESSING NUMBERS CHAPTER 9. DYNAMICS
104
CHAPTER 9. DYNAMICS 9.4. AN IKS INTERPRETER
• The == instance only yields True if two sets contain exactly the same elements.
• nrOfElts yields the number of elements that are in the set; isEmptySet only yields
True for the empty set (the empty set has zero elements).
• memberOfSet determines whether the first argument is an element in the set; isSubset
determines whether the first argument is a subset of the second argument (each
element in the first argument is also present in the second argument); isStrictSubset
determines whether the first argument is a strict subset of the second argument
(each element in the first argument is also present in the second argument, but the
second argument contains elements that are not present in the first argument);
• union calculates the union of the two sets; intersection calculations the intersection
of the two sets; without removes all elements present in the second set from the
first set.
Set is an instance of == and toString, and thus also of class Set. This module enables you
to create sets of sets.
105
9.4. AN IKS INTERPRETER CHAPTER 9. DYNAMICS
Dynamics
Write a program that yields three file-dynamics in the same directory as the application:
1. A file with name “I” and content dynamic [[I]].
i :: a -> a
ix=x
...
After execution, the files “I.dyn”, “K.dyn”, and “S.dyn” are added to the directory.
Parsing
The next step is parsing a line of input. Introduce the following algebraic data type IKS:
:: IKS = I | K | S | N Int | App IKS IKS
This type represents the syntax tree of the expression that was input. The symbols
’I’, ’K’, and ’S’ correspond with the alternatives I, K, and S. The integers correspond
with the alternative N. Note that while parsing, x expressions, like λ expressions, are
left-associative. For example, this means that the expressions SKI, (SK)I, and ((S)K)I
have the same meaning.
Some hints for this part:
• Convert each String that is to be parsed to a [Char]. To do this, use the overloaded
function fromString. Do not forget to eliminate the closing newline character!
• Write the function pIKS :: [Char] -> IKS that parses one line of input. If you want
to handle erroneous input, you can define the type of this function as pIKS :: [Char]
-> Maybe IKS. This is optional: you are allowed to assume only valid input is given.
106
CHAPTER 9. DYNAMICS 9.4. AN IKS INTERPRETER
For example:
split_bracket [’(SK)I’] ⇒ ([’(SK)’],[’I’]).
split_bracket [’((S)K)I’] ⇒ ([’((S)K)’],[’I’]).
Interpreting
A IKS syntax tree can be interpreted according to the [[·]] function described in the
introduction of this exercise. Write an interpreter function interp that receives as
arguments dynamics corresponding with the combinators I, K, and S; and the IKS syntax
tree of the parsed expression. The result of interp is a dynamic corresponding to the
interpreted function. Thus, its type is:
interp :: (Dynamic,Dynamic,Dynamic) IKS -> Dynamic
def
Use the function dynApply for application of symbols ([[e1 e2 ]] = ([[e1 ]] [[e2 ]])):
dynApply :: Dynamic Dynamic -> Dynamic
dynApply (f :: a -> b) (x :: a) = dynamic f x :: b
dynApply _ _ = dynamic "dynamic type error"
Console
In this part you combine the parts above into an I/O console program. The user can
input x expressions in the console. The program prompts with the interpretation of the
x expression that was input. To this end, the program first reads the three dynamics
that were written to disk in 9.4. To read these dynamics, use the function readDynamic.
E.g.:
# (ok,dyn_I,world) = readDynamic "I" world
107
9.4. AN IKS INTERPRETER CHAPTER 9. DYNAMICS
108
Chapter 10
SoccerFun
109
10.4. TRAINING: DEEP PASSING CHAPTER 10. SOCCERFUN
import Footballer
import Footballer
as has already been done in the framework for TeamMiniEffie. You may choose the starting
configuration yourself. The argument of type Home indicates which field half you are
assigned (West or East). You make your team accessible by adding the following lines to
the frame in module Team.icl (here in correspondence with module PeterAchten.icl):
implementation module Team
110
CHAPTER 10. SOCCERFUN 10.6. FINAL ASSIGNMENT
...
import PeterAchten // do not forget to import your module
...
allAvailableTeams = [ Team_MiniEffies
, TeamPeterAchten // this makes your team known
]
Goalkeeper: This player is not allowed to leave the penalty area. If the ball is reasonably
within reach, the goalkeeper should intercept it. “Reasonably within reach” means
that the goalkeeper should take into account her distance to the ball and its speed,
and the distance and speed of other players to the ball. If the goalkeeper is able to
reach the ball before an opponent, the player is obligated to play the ball. This
depends on the strategy of the goalkeeper’s team players: the goalkeeper is not
obligated to get in the way of team players.
Fielder: The field players may not all chase the ball. Instead, they should assume a
reasonable field division. “Reasonable field division” means that all fielders strive
to play a certain area of the field, and that the shared overlap between these
areas is small. Depending on the game situation (e.g. attacking and defending)
these positions should be assumed. Fielders may not possess the ball continuously;
they are required to play the ball to other players if this is reasonably sensible.
“Reasonably sensible” means that when a team player is in a sufficiently better area
than the player themselves, and if that team player is able to receive the ball, the
ball should be passed.
Rules of the game: Soccer players should respect decisions of the referee. This means
that your team is not allowed to play the ball or take possession of it if the opposing
team has right to it (e.g. during a throw-in, goal kick, corner, etc.). This also
means that a team is required to play the ball or take possession of it if the referee
indicates as such (similar situations).
Efficiency: For all soccer players it should hold that the brain function is sufficiently
efficient, meaning that if the team you created were to play against itself, the
calculation of the soccer actions of all 22 player together should not take more time
than one twentieth of a second. This can be checked by means of the frame-rate
indicator : the number behind the text Rounds/sec:. This number, during normal
speed, may not drop below 20 (unless a referee dialogue interrupts te game). Note
that you can set the game speed from the menu.
111
10.6. FINAL ASSIGNMENT CHAPTER 10. SOCCERFUN
112