ca320: functional programming and haskellygraham/ca320/t03_functional.pdfericsson: axe phone switch...

112
CA320: Functional Programming and Haskell Yvette Graham Semester 1, 2018 Yvette Graham CA320: Functional Programming and Haskell

Upload: others

Post on 21-Mar-2020

31 views

Category:

Documents


0 download

TRANSCRIPT

CA320: Functional Programming and Haskell

Yvette Graham

Semester 1, 2018

Yvette Graham CA320: Functional Programming and Haskell

Functional Programming Introduction

Programming Paradigms

Two major programming paradigms:

Imperative programming:

Instructions are used to change the computer’s state, e.g. x:=x+1Order of evaluation is given by sequencing of instructionsSumming the integers 1 to 10:

int total= 0;for (int i=1; i<=10; i++)

total=total+i;

Functional programming:

Expressions are used to give the program a value, e.g. f(g(x))Order of evaluation is given by dependencies of expressionsSumming the integers 1 to 10: sum [1..10]

Geoff Hamilton (DCU) CA320: Computability and Complexity 1 / 106

Functional Programming Introduction

Functional Programming

Some properties of functional programming languages:

allow the programmer to take a high-level view of what is to becomputed rather than howprograms are built out of expressions (whose meanings arevalues)there is no notion of a state (i.e. no variables or assignment)there is no sequencingfunctions do not have any side effects (any effect other than toreturn the value of the function)

Geoff Hamilton (DCU) CA320: Computability and Complexity 2 / 106

Functional Programming Introduction

Functional Programming

When we remove updateable variables from a programminglanguage, there are a few other casualties.In particular, the iterative constructs like while and for loops nolonger make sense, since we cannot change the variables thatmight appear in the conditions that control the iteration.Hence the only way to implement iteration in a functional languageis through the use of recursive functions.The following recursive function calculates factorials:fact n = if n == 0 then 1 else n * fact (n-1)

This compares with the following imperative definition:int fact(int n) {

int x = 1;while (n > 0) { x = x * n; n--; }return x;

}

Geoff Hamilton (DCU) CA320: Computability and Complexity 3 / 106

Functional Programming Introduction

Referential Transparency

When we use an expression we are only interested in its value(and for functions, in its return value); nothing else can have anyeffect on the rest of our program.This property is known as referential transparencyBasically, if we take an expression anywhere in a program, andreplace it with another expression that gives the same value, wewill not have changed the overall result of the program.

Geoff Hamilton (DCU) CA320: Computability and Complexity 4 / 106

Functional Programming Introduction

Functional Programming: Why?

At first sight a language without variables, assignment andsequencing looks very impractical.We will show in these lectures how a lot of interestingprogramming can be done in the functional style.Imperative programming languages have arisen as an abstractionof hardware, from machine code, through assemblers and macroassemblers, to C, Java and beyond.Perhaps this is the wrong approach and we should approach thetask by modelling the problem rather than the solution.

Geoff Hamilton (DCU) CA320: Computability and Complexity 5 / 106

Functional Programming Introduction

Advantages

If x = f (1), are the following true?x +x = f (1)+ f (1)f (1) = f (1)x + f (1) = f (1)+x

Not if you are using an imperative language!These equalities all hold for functional languages becausefunctions cannot have side-effects.This makes functional programs easier to reason about.It also makes functional programs easier to run concurrently.Writing programs in a functional style also promotes goodprogramming practice.

Geoff Hamilton (DCU) CA320: Computability and Complexity 6 / 106

Functional Programming Introduction

Disadvantages

Functional programming is not without its deficiencies.Some things are harder to fit into a purely functional model:

Input-outputInteractive or continuously running programs (e.g. editors, processcontrollers)

Functional languages also correspond less closely to currenthardware, so they can be less efficient, and it can be hard toreason about their time and space usage.

Geoff Hamilton (DCU) CA320: Computability and Complexity 7 / 106

Functional Programming Introduction

Functional Programming in the Real World

Some examples of functional programming in the real world:

Google: MapReduce, SawzallEricsson: AXE phone switchFacebook: Sigma spam-killer and Infer static analyserPerl 6DARCSXMonadYahooTwitterGarbage collection

Geoff Hamilton (DCU) CA320: Computability and Complexity 8 / 106

Functional Programming Introduction

The Rise of Functional Programming

Erlang, F# and Scala are attracting a lot of interest from developers.

Features from functional languages are also appearing in otherlanguages:

Garbage collection: Java, C#, Python, Perl, Ruby, JavascriptHigher-order functions: Java, C#, Python, Perl, Ruby, JavascriptGenerics: Java, C#List comprehensions: C#, Python, Perl 6, JavascriptType classes: C++ ‘concepts’

Geoff Hamilton (DCU) CA320: Computability and Complexity 9 / 106

Functional Programming GHCi

Haskell

The Haskell programming language was designed by SimonPeyton-Jones et al and is named after Haskell B. Curry who didpioneering work on combinatory logic (similar to the λ -calculus, themathematical theory on which functional programming is based).

Haskell is:

(almost) a purely functional language with no side-effects andreferential transparency (with the exception of input/output);a lazy programming language, in that it does not evaluate anexpression unless it really has to;a statically typed language so that at compile time we know thetype of each expression, and has a type inference system thattries to work out the type of an expression if it has not beenexplicitly specified.

Geoff Hamilton (DCU) CA320: Computability and Complexity 10 / 106

Functional Programming GHCi

Glasgow Haskell Compiler (GHC)

GHC is the leading implementation of Haskell, and comprises acompiler and interpreter.The interactive nature of the interpreter GHCi makes it well suitedfor teaching and prototyping.GHC is freely available from: www.haskell.org/platform.The interpreter can be started from the terminal command prompt$ by simply typing ghci:

$ ghci

GHCi, version X: http://www.haskell.org/ghc/ :? for help

>

The GHCi prompt > means that the interpreter is now ready toevaluate an expression.

Geoff Hamilton (DCU) CA320: Computability and Complexity 11 / 106

Functional Programming GHCi

GHCi

GHCi can be used as a desktop calculator to evaluate simple numericexpresions:

> 2+3*414

> (2+3)*420

> sqrt (3ˆ2 + 4ˆ2)5.0

GHC knows about lots of functions and operators (such as + and *)that can be included in expressions. They are defined in the Preludewhich is just a Haskell program.

Geoff Hamilton (DCU) CA320: Computability and Complexity 12 / 106

Functional Programming GHCi

GHCi

As well as the functions in the Prelude, you can also define yourown functions.New functions are defined within a script, a text file comprising asequence of definitions.By convention, Haskell scripts usually have a .hs suffix on theirfilename.

This is not mandatory, but is useful for identification purposes.

When developing a Haskell script, it is useful to keep two windowsopen, one running an editor for the script, and the other runningGHCi.By loading a script into GHCi, you can use the session to evaluateexpressions containing functions and operators defined in thescript, as well as those in the Prelude.

Geoff Hamilton (DCU) CA320: Computability and Complexity 13 / 106

Functional Programming GHCi

Useful GHCi Commands

Command Meaning

:load name load script name:reload reload current script:set editor name set editor to name:edit name edit script name:edit edit current script:type expr show type of expr:? show all commands:quit quit GHCi

Geoff Hamilton (DCU) CA320: Computability and Complexity 14 / 106

Functions

inputs output12

46

12

34add

A function is something which we can picture as a box with some inputsand an outputs above

The function gives exactly one output value which depends upon theinput value(s).

We will often use the term result for the output, and the terms argumentsor parameters for the inputs.

A simple example of a function is addition, +, over numbers.

Given input values 12 and 34 the corresponding output will be 46.

In a functional programming language functions will be the centralcomponent of our models

Yvette Graham CA320: Functional Programming and Haskell

Types

A type is a collection of values, such as numbers or images, grouped togetherbecause although they are different – 2 is not the same as 567 – they are thesame sort of thing, in that we can apply the same functions to them. Functions

operate over particular types: e.g. a function to scale an image will take twoarguments, one of type Image and the other of type Int

Haskell syntax to declare the type of a variable:

<variable name> :: <type>

Double colon :: means ‘is of type’ in Haskell

Example:

answer :: Int

Yvette Graham CA320: Functional Programming and Haskell

Definitions in Haskell

“=” is used to make definitions in Haskell

Example, declare ‘answer’ as type Int and define it as 40:

answer :: Int

answer = 40

Example, declare ‘size’ as type Int and define it as 12 + 13:

size :: Int

size = 12 + 13

Example, declare ‘blackHorse’ as type Image and the Image associated with blackHorseis obtained by applying the function invertcolour to the Image associated with ‘horse’:

blackHorse :: Image

blackHorse = invertcolour horse

Yvette Graham CA320: Functional Programming and Haskell

Defining Functions in Haskell

Basic Syntax for defining a function in Haskell:

<function name> :: <function type>

<function name> <names of arguments> = <result>

Example, square is a function taking a single argument fromintegers to integers (Int− >Int):

square :: Int -> Int

square x = x * x

Yvette Graham CA320: Functional Programming and Haskell

Function Definition with Multiple Arguments

Example, allEqual is a function that takes three integer argumentsand produces a Boolean result

allEqual :: Int -> Int -> Int -> Bool

allEqual n m p = (n==m) && (m==p)

The first line above gives the function name followed by :: (isdefined by) followed by its 3 argument types (Int − >) and theresult type (Bool)

The second line defines what actually takes place in thefunction

It gives the function name, lists the names of the 3 arguments(n m p) and defines the result as the conditional expression(n==m) && (m==p) which will return a Boolean value

Yvette Graham CA320: Functional Programming and Haskell

Guards in Haskell

Guards (or tests) in Haskell are Boolean expressions that liebetween ∣ and = guarding which option is to be chosen

Guard Syntax:

| <condition> = <result>

| otherwise = <result>

Example, function maxi is to return the maximum of its twointeger arguments

maxi :: Int -> Int -> Int

maxi n m

| n>=m = n

| otherwise = m

Reading of the above: if n>=m return n otherwise return m

Yvette Graham CA320: Functional Programming and Haskell

Guards with Multiple Conditions

You can list multiple conditions within a Haskell Guard (similar toelse if in other langauges)

Guard Syntax with Multiple Conditional Statements:

| <condition 1> = <result>

| <condition 2> = <result>

...

| otherwise = <result>

Example, function maxi is to return the maximum of its twointeger arguments

maxi :: Int -> Int -> Int

maxi n m

| n>m = n

| n==m = n

| otherwise = m

Yvette Graham CA320: Functional Programming and Haskell

Comments in Haskell

Single-line comments start with −− and end with the end of theline Example:

-- square the value given by x

Multi-line comments begin with {- and end with -}

{-

square the value

given by x

-}

Yvette Graham CA320: Functional Programming and Haskell

Errors in Haskell

Evaluation will halt and you will get an error message whensomething unexpected happens running built-in functions.

To make a function you define return an explicit error message andhalt evaluation, you can call the built-in error function. Example:

oneRoot a b c

| ...

| otherwise = error "error in oneRoot function"

If that part of the program is reach the evaluation will halt and theerror message you defined will be printed to screen.

Yvette Graham CA320: Functional Programming and Haskell

Functional Programming GHCi

Identifiers

Function and argument identifiers must begin with a lower caseletter.This can be followed by any sequence of letters, digits,underscores ( ) and primes (′).Examples: myFun, fun1, arg 2, x’By convention, list arguments usually have an s suffix on theirname. For example: xs, ns, xssYou cannot use a name that has already been used; this includesall the functions in the prelude. For example: sqrt, head, tail,length

Some names are reserved words, and have a special meaning inHaskell. You cannot use them for any other purpose. For example:if, then, else, case

Geoff Hamilton (DCU) CA320: Computability and Complexity 16 / 106

Functional Programming Types

Types

In Haskell, every well formed expression has a type, which can beautomatically calculated at compile time using a process calledtype inference.All type errors are found at compile time, which makes programssafer and faster by removing the need for type checks at run time.Values can be of a basic type such as integer, boolean etc. or acompound type. That is, made up of values of other types.Examples of basic types:

value type-273 Int integers3.14 Float floating-point numbersTrue Bool truth values’A’ Char characters

Geoff Hamilton (DCU) CA320: Computability and Complexity 17 / 106

Functional Programming Types

Types

Examples of compound types:value type(7,’Z’) (Int,Char) tuple[1,2,9] [Int] list"Hello" String string (list of characters)square Int -> Int function

Types are useful because some functions only make sense on certaintypes. For example the expression square ’A’ would give a typeerror.

Geoff Hamilton (DCU) CA320: Computability and Complexity 18 / 106

Functional Programming Types

Numbers

Numbers in Haskell can be integers (of type Int or Integer) orfloats (of type Float).Floats must have a fractional part (e.g. 3.0) or an exponential part(e.g. 3E7), or both (e.g. 3.0E7).The following infix operators for numbers are built-in (the normalprecedences apply):

+ integer or real addition- integer or real subtraction* integer or real multiplication/ real divisiondiv, mod integer division and remainder

Geoff Hamilton (DCU) CA320: Computability and Complexity 19 / 106

Functional Programming Types

Examples

> 3+4*627> (3+4)*642> div 5 22> 5 ‘mod‘ 2;1> 65/416.25

Note the use of ‘‘ to use a function in infix position

Geoff Hamilton (DCU) CA320: Computability and Complexity 20 / 106

Functional Programming Types

Booleans

Booleans (of type Bool) can have the values True or False.The following operators for booleans are built-in (the normalprecedences apply):

&& logical and|| logical ornot logical negation

The relational operators which can be used to give a booleanresult are ==, /=, >, >=, <, <=Equality can be tested for most types (except functions).

Geoff Hamilton (DCU) CA320: Computability and Complexity 21 / 106

Functional Programming Types

Examples

> TrueTrue> False || TrueTrue> 4 > 1+2True> 3>6 || 3>2True> 3>6 && 3>2False

Geoff Hamilton (DCU) CA320: Computability and Complexity 22 / 106

Functional Programming Types

Characters

Values of type Char are individual Unicode characters .Literal characters are written within single quotes: ’a’ ’R’ ’4’’$’

Some special characters are represented as follows:\t tab\n new line\f form feed\b back space\\ back slash\’ single quote

Geoff Hamilton (DCU) CA320: Computability and Complexity 23 / 106

Functional Programming Types

Strings

Literal strings are written in double quotes. For example:"Hello, 007!" "a" "Here is a \nnew line."

If you enter a string as an expression at the GHCi prompt, it givesthe string back as its value. For example:

> "This is a \nnew line.""This is a \nnew line."

If you want the string itself to be output, use the standard functionputStr as follows:

> putStr "This is a \nnew line."This is anew line.

Geoff Hamilton (DCU) CA320: Computability and Complexity 24 / 106

Functional Programming Types

Built-In Functions

Some examples of built-in functions in Haskell:

abs absolute valueceiling, floor, round rounding up, down or to closest integersqrt square rootsin, cos, tan trigonometric functionsacos, asin, atan inverse trigonometric functionsexp powers of elog logarithm to base enegate change the sign of a number

Geoff Hamilton (DCU) CA320: Computability and Complexity 26 / 106

Functional Programming Functions

Where Clauses

where clauses allow local definitions within a function.These definitions can be used from anywhere within the function.For example:points :: Int -> Int -> Intpoints for against| diff > 0 = 3| diff == 0 = 1| otherwise = 0where diff = for - against

It is important to remember that the definition diff only appliesinside the definition of points; this is why it is called a localdefinition.

Geoff Hamilton (DCU) CA320: Computability and Complexity 51 / 106

Associativity

Some operators are associative, e.g. 4 + 5 is the same as 5 + 4

There are many non-associative operators, however

Non-associative operator example: In any programming language, we couldhave the following

4 − 2 − 1

If this expression was evaluated as (4-2)-1 = 1 ... this would mean minusis left associative in that language

If this expression was evaluated as 4-(2-1) = 3 ... this would mean minusis right associative in that language

In Haskell, operators are each classified as either left or right associative, whichhelps when we have multiple operators of the same precedence without braces:

2 ˆ 3 ˆ 2

ˆ is right associative, so it is evaluated as 2 ˆ (3 ˆ 2) = 512 ... asopposed to (2 ˆ 3) ˆ 2 = 64

+ and - are left associative in Haskell, the associativity of each operatoris listed in the Appendix E of the reading material

Yvette Graham CA320: Functional Programming and Haskell

Function Application in Haskell

Function application in Haskell:

is treated just like any other operator

has higher priority than all other operators

is left associative

Example:

add 3 4 + 5 is evaluated as (add 3 4) + 5

... as opposed to add 3 (4 + 5)

Yvette Graham CA320: Functional Programming and Haskell

Polymorphic Functions

Think back to an ordinary function definition in Haskell:

allEqual :: Int -> Int -> Int -> Bool

allEqual n m p = (n==m) && (m==p)

Ordinarily you define a function giving its argument types

Polymorphic functions in Haskell allow you to take in lists ofvariable type, i.e. the function can be used on lists of any type

Polymorphic Function taking a list of any type as input uses thefollowing syntax

<function name> :: [t] -> <return type>

<function name> [] = <base case>

<function name> (a:x) = <other cases>

Yvette Graham CA320: Functional Programming and Haskell

Polymorphic Functions

We said that polymorphic Function taking a list of any type asinput uses the following syntax

<function name> :: [t] -> <return type>

<function name> [] = <base case>

<function name> (a:x) = <other cases>

Notice the name of the list type is t but this can be any name youchoose as long as it starts with a lowercase, a good convention isto use the single characters t, u or v, however

Example: return the length of a list of any type

length :: [t] -> Int

length [] = 0

length (a:x) = 1 + length x

Yvette Graham CA320: Functional Programming and Haskell

Class Constraints

In Haskell, we can apply class constraints to function definitions

(+) :: Num a => a -> a -> a

The above is how to declare that the ‘+’ operator takes anytwo arguments of type a, returns an argument of the sametype a, where a must be a numeric typeThe following syntax sets the type of a to a number:Num a =>

Example:

> 1 + 2

3

> 1.0 + 2.0

3.0

> ’a’ + ’b’

ERROR

Yvette Graham CA320: Functional Programming and Haskell

Functional Programming Types

Type Classes

Haskell has a number of type classes, including:

Num - numeric types. The Num class includes all types that act asnumbers.Eq - equality types. The Eq class includes all standard Haskelltypes except IO and functions.Ord - ordered types. Most Haskell standard types belong to Ordexcept function types and some abstract types.Show - types which can be displayed as strings. Most Haskellstandard types belong to Show except function types.

For example:

(+) :: Num a => a -> a -> a(==) :: Eq a => a -> a -> Bool(<) :: Ord a => a -> a -> Boolshow :: (Show a) => a -> String

Geoff Hamilton (DCU) CA320: Computability and Complexity 29 / 106

Functional Programming Lists

Lists

A list is a sequence of elements of the same type. For example:[1,2,3] is of type [int]["Smith","Jones","Brown"] is of type [String]

An empty list is represented by []The infix operator : (cons) makes a list by putting an element infront of an existing list. For example:1:2:3:4:[]

== 1:2:3:[4]== 1:2:[3,4]== 1:[2,3,4]== [1,2,3,4]

Geoff Hamilton (DCU) CA320: Computability and Complexity 30 / 106

Functional Programming Lists

Lists

Every list is either empty or has the form h:t, where h is calledthe head of the list and t is called the tail.The built-in functions head and tail can be used to extract thehead and tail respectively from a list. For example:> head [1,2,3,4]1> tail [1,2,3,4][2,3,4]

The infix operator ++ (append) joins two lists together:> [1,2,3] ++ [4,5,6][1,2,3,4,5,6]

The infix operator !! selects the list element at a given index(starting from 0):> [1,9,37,8] !! 237

Geoff Hamilton (DCU) CA320: Computability and Complexity 31 / 106

Functional Programming Lists

Some Useful List Functions

last: returns the last element of a list:> last [2,4,6,8]8

init: returns all elements of the list except the last:> init [2,4,6,8][2,4,6]

length: returns the number of elements in a list:> length [2,4,6,8]4

null: indicates whether or not a list is empty:> null [2,4,6,8]False

Geoff Hamilton (DCU) CA320: Computability and Complexity 32 / 106

Functional Programming Lists

Some Useful List Functions

reverse: reverses the order of elements in a list:> reverse [2,4,6,8][8,6,4,2]

take: takes the specified number of elements from the start of alist:> take 2 [2,4,6,8][2,4]

drop: drops the specified number of elements from the start of alist:> drop 3 [2,4,6,8][8]

maximum: returns the largest element of a list:> maximum [2,8,4,9,6]9

Geoff Hamilton (DCU) CA320: Computability and Complexity 33 / 106

Functional Programming Lists

Some Useful List Functions

minimum: returns the smallest element of a list:> minimum [2,8,4,9,6]2

sum: returns the sum of all the elements of a list:> sum [2,8,4,9,6]29

product: returns the product of all the elements of a list:> product [2,8,4,9,6]384

elem: tests if the specified element is contained in a list:> elem 4 [2,8,4,6]True

Geoff Hamilton (DCU) CA320: Computability and Complexity 34 / 106

Functional Programming Lists

List Ranges

Haskell provides a special ‘ellipsis’ or ‘dot-dot’ syntax for listranges. For example:[1..5] == [1,2,3,4,5]

To specify step sizes other than 1, give the first 2 numbers. Forexample:[1, 3..10] == [1,3,5,7,9][0,10..50] == [0,10,20,30,40,50]

We can also have list ranges with characters. For example:[’a’..’z’] == "abcdefghijklmnopqrstuvwxyz"

We can also have infinite lists. For example:[1..] == [1,2,3,4,5,6, ...[1,3..] == [1,3,5,7,9, ...

Geoff Hamilton (DCU) CA320: Computability and Complexity 35 / 106

Functional Programming Functions

Expression Evaluation

Haskell evaluates an expression by repeatedly performingsimplifications (or reductions) on the expression until no morereductions can be performed.When an expression is fully reduced in this way it is said to be innormal form.Examples of the different kinds of reduction are:

replacing a built-in function application with its valuereplacing a user-defined function name with its body andsubstituting the actual parameters into it to replace thecorresponding identifiers

For example:square (2*(3+4))

=> (2*(3+4)) * (2*(3+4))=> (2*7) * (2*7)=> 14 * 14=> 196

Geoff Hamilton (DCU) CA320: Computability and Complexity 41 / 106

Functional Programming Functions

Functions

So far we have only seen simple function definitions like:square :: Int -> Int

square x = x * x

When GHC finds an expression matching the left-hand side of adefinition, the evaluation mechanism simply replaces it by theright-hand side. For example:

square 3 + 1 ⇒ (3 * 3) + 1 ⇒ 9 + 1 ⇒ 10

Haskell provides more than just simple expressions as definitions,e.g.

guards and alternative right hand sidespatterns and alternative left hand sideslocal definitions (i.e. where clauses)

Geoff Hamilton (DCU) CA320: Computability and Complexity 42 / 106

Functional Programming Functions

Recursion

Many functions can naturally be defined in terms of otherfunctions. For example:fac :: Int -> Int

fac n = product [1..n]

fac 4=> product [1..4]=> product [1,2,3,4]=> 1 * 2 * 3 * 4=> 24

Geoff Hamilton (DCU) CA320: Computability and Complexity 43 / 106

Functional Programming Functions

Recursion

In Haskell, functions can also be defined in terms of themselves.Such functions are called recursive. For example:fac :: Int -> Int

fac n = if n==0 then 1 else n * fac (n-1)

fac 3=> 3 * fac 2=> 3 * (2 * fac 1)=> 3 * (2 * (1 * fac 0))=> 3 * (2 * (1 * 1))=> 3 * (2 * 1)=> 3 * 2=> 6

Geoff Hamilton (DCU) CA320: Computability and Complexity 44 / 106

Functional Programming Functions

Recursion

This function uses a conditional expression. In Haskell, thesemust always have an else branch.To prevent an infinite loop, a recursive function must have a basecase.The base case for this function is when the parameter n is equalto 0.There must also be an inductive case in which the parameters ofthe recursive function call move closer to the base case.In the inductive case for this function, the parameter is reduced byone moving it towards the base base.This function can still have an infinite loop for negative integersbecause the base case is never reached.

Geoff Hamilton (DCU) CA320: Computability and Complexity 45 / 106

Functional Programming Functions

Guards

As an alternative to conditionals, functions can also be definedusing guarded equations:fac :: Int -> Intfac n| n==0 = 1| n/=0 = n * fac (n-1)

There are two alternative right-hand sides, and a guard for each ofthem.GHC evaluates each guard in turn, first to last, until it finds onethat equals True.The left-hand side is then replaced by the right hand side thatcorresponds to that true guard.If none of the guards are true, GHC will report an error.Therefore it is wise to ensure that one guard will always be true.

Geoff Hamilton (DCU) CA320: Computability and Complexity 46 / 106

Functional Programming Functions

Guards

Haskell provides a special guard otherwise that is always true.otherwise is used as a catch-all guard at the end of a sequenceof alternatives:fac :: Int -> Intfac n| n==0 = 1| otherwise = n * fac (n-1)

Here is another example. How many points does a team get,given the score at the end of the match?points :: Int -> Int -> Intpoints for against| for > against = 3| for == against = 1| otherwise = 0

Geoff Hamilton (DCU) CA320: Computability and Complexity 47 / 106

Functional Programming Functions

Pattern Matching

Function definitions can consist of a number of separate clauseswith patterns on the left-hand sides of the equations.The simplest patterns are just constant values.For example:fac :: Int -> Intfac 0 = 1fac n = n * fac (n-1)

It is always possible to use guards, but patterns often makedefinitions more readable.It is important to realise that patterns are not arbitraryexpressions. For example, this is illegal:squareRoot (x*x) = x

This does not tell how to compute the square root function;patterns and expressions are not the same thing.

Geoff Hamilton (DCU) CA320: Computability and Complexity 48 / 106

Functional Programming Functions

Pattern Matching

Pattern matching can also be performed on lists. For example:myLength :: [a] -> IntmyLength [] = 0myLength (_:xs) = 1 + myLength xs

The base case is the empty list.If the list we are matching is not the empty list we try the nextpattern :xs

The list is matched against :xs, which is a list with something atits head (start) followed by a tail.The tail of the list is matched with xs.The head of the list is matched with . We use when we don’t careabout the value we are matching against.

Geoff Hamilton (DCU) CA320: Computability and Complexity 49 / 106

Functional Programming Functions

Pattern Matching

Pattern matching can also be performed on tuples. For example:addPair :: (Int, Int) -> IntaddPair (x,y) = x + y

The pattern (x,y) matches any pair and sets x to be the firstelement of the pair and y to be the second element.Example: (0.0, a) matches any pair where the first element isthe Float value 0.0. The second element of the pair is bound toa.Example: (0, (’5’, answer)) matches any pair whose firstelement is 0 and whose second element is a pair whose firstelement is the character ’5’. The second element of the secondelement is bound to answer.

Geoff Hamilton (DCU) CA320: Computability and Complexity 50 / 106

Functional Programming Functions

Where Clauses

Pattern matching can also be performed in where clauses. Forexample:initials :: String -> String -> Stringinitials xs ys = [x] ++ ". " ++ [y] ++ "."

where (x:_) = xs(y:_) = ys

Local functions can also be defined in where clauses. Forexample:initials :: String -> String -> Stringinitials xs ys = ini xs ++ ". " ++ ini ys ++ "."

where ini (x:_) = x

Geoff Hamilton (DCU) CA320: Computability and Complexity 52 / 106

Functional Programming Functions

Let Expressions

let expressions also allow local definitions. For example:

cylinderArea :: (RealFloat a) => a -> a -> acylinderArea r h =

let sideArea = 2 * pi * r * htopArea = pi * rˆ2

in sideArea + 2 * topArea

let expressions are more local in scope, and cannot be usedacross multiple clauses in a function definition.let expressions can be used anywhere other expressions can beused, so we can write expressions such as:4 * (let a = 9 in a + 1) + 2

Geoff Hamilton (DCU) CA320: Computability and Complexity 53 / 106

Functional Programming Functions

Case Expressions

case expressions are similar to case statements from imperativelanguages but with pattern matching added:

headOfList :: [a] -> aheadOfList xs = case xs of [] -> error "Empty!"

(x:_) -> x

Whereas pattern matching on function parameters can only be donewhen defining functions, case expressions can be used anywhereother expressions can be used. For example:

describeList :: [a] -> StringdescribeList xs = "The list is " ++

case xs of [] -> "Empty"[x] -> "Singleton"xs -> "Longer List"

Geoff Hamilton (DCU) CA320: Computability and Complexity 54 / 106

Functional Programming Layout

Layout

In any programming language, the layout of your program isimportant for the readability of your programs.In Haskell, layout rules help to get rid of the annoying punctuationused in many other languages (semicolons, braces, begin/end,etc.).Haskell uses indentation to decide the ends of definitions,expressions and so on.Basic Rule: A definition ends when a (non-space) symbol appearsin the same column as the first symbol of the definition.Here are some examples:myMin x y|x<y=x|otherwise=yThis is correct, but ugly and hard to read.myMin x y| x < y = x| otherwise = y

This is better.Geoff Hamilton (DCU) CA320: Computability and Complexity 55 / 106

Functional Programming Layout

Layout

cube x= x *x * x

answer = 6 * 7

This is correct. The definition of cube ends when the ‘a’ of answerappears in the same column as the ‘c’ of cube.

cube x= x *x * x

This is wrong. The definition of cube is ended by (before) the equalsign. GHC will detect this as a syntax error:

ERROR "cube.hs":2:1: parse error on input ’=’Geoff Hamilton (DCU) CA320: Computability and Complexity 56 / 106

Functional Programming Layout

Layout

cube x= x * x * x

answer = 6 * 7

and:

cube x= x * x * x

answer = 6 * 7

Both of these are wrong since answer does not line up under cube.Note that this must hold even if sections of code are separated bycomments.

Geoff Hamilton (DCU) CA320: Computability and Complexity 57 / 106

Functional Programming Layout

Layout

You should adopt the following layout of function definitions asstandard:

fun p_1 p_2 .. p_n| guard_1 = e_1| guard_2 = e_2...

| guard_k = e_kwherelocal_1 a_1 ... a_m = r_1local_2 = r_2...

You don’t need to follow this exactly but the main thing is to adopt auniform style.

Geoff Hamilton (DCU) CA320: Computability and Complexity 58 / 106

Functional Programming Type Checking

Type Checking

Strongly typed languages such as Java give programmerssecurity by restricting their freedom to make mistakes.Weakly typed languages such as Prolog allow programmers morefreedom and flexibility with a reduced amount of security.The Haskell type system gives the security of strong typechecking as well as greater flexibility.Every expression must have a valid type, but it is not necessaryfor the programmer to include type information

this is inferred automatically.

Geoff Hamilton (DCU) CA320: Computability and Complexity 59 / 106

Functional Programming Type Checking

Type Checking

Given little or no explicit type information, Haskell can infer all thetypes associated with a function definition. For example:

the types of constants can be inferred automaticallyidentifiers must have the same type everywhere within anexpression

Other type checking rules are applied for each form of expression.For example, consider if expressions:if C then E1 else E2

E1 and E2 must have the same type, and C must have type Bool.

Geoff Hamilton (DCU) CA320: Computability and Complexity 60 / 106

Functional Programming Type Checking

Type Checking

Consider the following function:isSpace c = c == ’ ’

The constant ’ ’ is a character.Because c is compared to a Char, it must also be a Char.The argument to isSpace must therefore be a Char.The body of isSpace is a comparison, so it must be of type Bool.The return type of isSpace is therefore a Bool.isSpace is therefore of type Char -> Bool.We can use GHCi to query the type of this function as follows::type isSpace

GHCi will respond as follows:isSpace :: Char -> Bool

Geoff Hamilton (DCU) CA320: Computability and Complexity 61 / 106

Functional Programming Type Checking

Type Checking

Type inference leaves some types completely unconstrained; insuch cases the definition is polymorphic (this is parametricpolymorphism).For example, consider the function reverse for reversing a list.The following type is inferred for this function:[a] -> [a]

This is an example of a polytype. These have type variables (e.g.a, t, ...).Types without type variables are called monotypes.A polytype can be thought of as standing for the collection ofmonotypes which can be obtained by instantiating the typevariables with monotypes (e.g. [Int], [Bool]).

Geoff Hamilton (DCU) CA320: Computability and Complexity 62 / 106

Functional Programming Type Checking

Type Checking

GHC can work out the type of any expression and any functionfrom its definition.Even if you declare the type of a function, GHC will work it outanyway and check whether you are right.You should always declare the type of any functions you aredefining in your programs:

The type of a function is a basic part of its design. How can you beclear about a function’s behaviour unless you at least know thetypes of its arguments and the type of its result?Type declarations are an important part of program documentation.Type declarations help you to find errors.

If GHC figures that the type of a function is different to what youexpect, then you have made an error.

Geoff Hamilton (DCU) CA320: Computability and Complexity 63 / 106

Functional Programming Type Checking

Type Checking

For example, suppose you wish to design a function myEven suchthat myEven x returns True if x is divisible by 2 and Falseotherwise as follows:myEven :: Int -> BoolmyEven x = x ‘div‘ 2

When loading a script containing this definition, GHCi will give anerror message:Couldn’t match expected type ’Bool’ with actual type ’Int’In the first argument of ’div’, namely ’x’In the expression: x ‘div‘ 2

GHC is indicating that it can tell from the definition that myEvenhas a type that is different from its declaration.If we had not declared its type, a less obvious error messagewould occur when we used myEven.

Geoff Hamilton (DCU) CA320: Computability and Complexity 64 / 106

Functional Programming List Functions

List Functions

Suppose we wanted to define a function to add together all theelements of a list of integers. (There is a standard function sumthat does this, but ignore that for the sake of the exercise.)mySum :: [Int] -> Int

For a fixed length it is easy:mySum [x,y,z] = x+y+z

But a function that works for any length?Induction (i.e. recursion) is the key to the solution.Find a base case and an inductive case:

For lists the base case is usually the empty list [].The inductive case is the non-empty lists, i.e. those consisting of ahead (element) and a tail (list): (x:xs).

Geoff Hamilton (DCU) CA320: Computability and Complexity 65 / 106

Functional Programming List Functions

List Functions

Design of mySum:The sum of the empty list is 0The sum of a non-empty list (x:xs) is x + (sum of xs)

For example:

mySum [1,9,37,8]=> 1 + mySum [9,37,8]=> 1 + (9 + mySum [37,8])=> 1 + (9 + (37 + mySum [8]))=> 1 + (9 + (37 + (8 + mySum [])))=> 1 + (9 + (37 + (8 + 0)))=> 55

Geoff Hamilton (DCU) CA320: Computability and Complexity 66 / 106

Functional Programming List Functions

List Functions

The inductive design of mySum translates directly into the followingHaskell definition.mySum [] = 0mySum (x:xs) = x + mySum xs

Notice how patterns are used to distinguish the empty andnon-empty cases.Notice how the second clause defines mySum in terms of mySumapplied to a “smaller” argument.The argument in the recursive calls get smaller and smaller until itis the empty list. So we escape the recursion through the firstclause (the base case).Notice how the definition is like mathematical induction: the basecase is lists of length 0 and the inductive case defines the functionfor lists of length n+1 in terms of the result for lists of length n.

Geoff Hamilton (DCU) CA320: Computability and Complexity 67 / 106

Functional Programming List Functions

List Functions

Example: a function to double every element of a list of integers.double [1,9,37,8] ⇒ [2,18,74,16]

Design:(base case) doubling all elements of [] returns [].(inductive case) multiply the head of the list by 2 and cons it ontodouble of the tail of the list.

double :: [Int] -> [Int]double [] = []double (x:xs) = (2*x) : double xs

Example: evaluate the following:double [1,9,37]

Geoff Hamilton (DCU) CA320: Computability and Complexity 68 / 106

Functional Programming List Functions

List Functions

Example: length is a standard function but how would we defineit ourselves?Design:

(base case) the length of [] is 0.(inductive case) a non-empty list is 1 element longer than its tail.

myLength :: [a] -> IntmyLength [] = 0myLength (x:xs) = 1 + length xs

Example: evaluate the following:myLength [1,9,37]

Geoff Hamilton (DCU) CA320: Computability and Complexity 69 / 106

Functional Programming List Functions

List Functions

Example: How would we define the (standard) list append function(++)?append :: [a] -> [a] -> [a]

This is trickier to design because append takes two lists.Should we try induction on the first list, the second list or both?For recursion on both lists we have the following 4 cases, thecombinations of base and inductive cases for each list:append [] [] = ...append [] (y:ys) = ...append (x:xs) [] = ...append (x:xs) (y:ys) = ...

If we start with this collection, we may find some redundanciesand eliminate them.

Geoff Hamilton (DCU) CA320: Computability and Complexity 70 / 106

Functional Programming List Functions

List Functions

The first two cases do not need to be separated. The followingclause covers both:append [] ys = ys

It is less obvious, but the third and fourth cases can also becombined:append (x:xs) ys = x:(append xs ys)

So in fact, recursion on just the first list is sufficient to define thisparticular function.Attempting to define append by induction on only the second listdoesn’t seem possible:append xs [] = xsappend xs (y:ys) = ?

The problem is that the basic list constructor (:) adds elements tothe front of a list. The pattern (y:ys) binds y to some elementthat will end up in the middle of the result list...

Geoff Hamilton (DCU) CA320: Computability and Complexity 71 / 106

Functional Programming List Functions

List Functions

Pattern-matching is a readable and powerful way of defining listfunctions in Haskell.Two warnings:

Patterns are not arbitrary expressions.Variables cannot be repeated on the left hand side of a definition.

For example:remDups (x:x:xs) = x:remDups xs

is illegal. So is:find code ((code,name,price):rest)= (name,price)

The solution is to use guards to test for equality, rather thanexpecting the pattern-matching to do it for you:find barcode ((code,name,price):rest)| barcode == code = (name,price)| ...

Geoff Hamilton (DCU) CA320: Computability and Complexity 72 / 106

Functional Programming List Functions

List Functions

Define a function:sumPairs :: [(Int,Int)] -> [Int]that sums the pairs of elements in a list. For example:sumPairs [(1,9),(37,8)] ⇒ [10,45]The base case is standard:sumPairs [] = []We can choose a variety of patterns for the inductive case.In general, it is best to choose the most precise pattern.Alternatives:sumPairs1 (p:ps) = (fst p + snd p):sumPairs1 ps

sumPairs2 (p:ps) = (m+n):sumPairs2 pswhere (m,n) = p

sumPairs ((m,n):ps) = (m+n):sumPairs ps

In the final alternative, the most precise pattern has been used,leading to the simplest and clearest definition of the function.

Geoff Hamilton (DCU) CA320: Computability and Complexity 73 / 106

Functional Programming List Functions

List Functions

Haskell attempts to match patterns in a strictly sequential order:left to right in a definition; andtop to bottom in a sequence of definition clauses.

Note that as soon as a pattern match fails, that equation isrejected and the next one tried. Consider:myZip :: [a] -> [b] -> [(a,b)]myZip (x:xs) (y:ys) = (x,y):myZip xs ysmyZip xs ys = []

If the first argument is [] then the match on (x:xs) fails and thatclause is rejected without attempting to match the secondargument. myZip truncates the longer of the two list arguments.

Geoff Hamilton (DCU) CA320: Computability and Complexity 74 / 106

Functional Programming List Functions

List Functions

myElem :: Eq a => a -> [a] -> BoolmyElem a [] = FalsemyElem a (x:xs)| a==x = True| otherwise = myElem a xs

myReverse1 :: [a] -> [a]myReverse1 [] = []myReverse1 (x:xs) = (myReverse1 xs) ++ [x]

Geoff Hamilton (DCU) CA320: Computability and Complexity 75 / 106

Functional Programming List Functions

List Functions

myReverse2 :: [a] -> [a]myReverse2 xs = myReverse2’ xs []wheremyReverse2’ [] zs = zsmyReverse2’ (y:ys) zs = myReverse2’ ys (y:zs)

myUpto :: Int -> Int -> [Int]myUpto m n| m>n = []| otherwise = m:(myUpto (m+1) n)

myConcat :: [[a]] -> [a]myConcat [] = []myConcat (xs:xss) = xs ++ (myConcat xss)

Geoff Hamilton (DCU) CA320: Computability and Complexity 76 / 106

Functional Programming List Functions

List Functions

Design a function that sorts a list of values into ascending order.isort :: Ord a => [a] -> [a]

For example: isort [7,3,9,2] ⇒ [2,3,7,9]

As in all previous examples, our first intuition should be the twoinductive cases:isort [] = ...isort (x:xs) = ...

The first case is easy since the empty list is trivially sorted:isort [] = []

Geoff Hamilton (DCU) CA320: Computability and Complexity 77 / 106

Functional Programming List Functions

List Functions

For non-empty lists we have direct access to the head x and thetail xs, e.g.:7:[3,9,2]

Typically, there is a recursive call of the function on the tail of thelist.Hence we might expect the second clause to look like:isort (x:xs) = ...(isort xs)...

For the example above, we have x equal to 7 and isort xsequal to [2,3,9]

To build the final result, isort (x:xs) from x and isort xs,we need to insert 7 into the correct position in [2,3,9].Hence we can write:isort (x:xs) = insert x (isort xs)

Geoff Hamilton (DCU) CA320: Computability and Complexity 78 / 106

Functional Programming List Functions

List Functions

To define the function insert, our intuition should be to try thestandard inductive approach:insert :: Ord a => a -> [a] -> [a]insert x [] = ...insert x (y:ys) = ...

To insert x into an empty list maintaining ascending ordering:insert x [] = [x]

If the list is non-empty, we have direct access to its head. We mayask: does x belong before or after the head of the list?

If x belongs before the head, simply cons it there and we’re done.If x belongs after the head, we can use a tail-recursive call ofinsert to find out where it belongs:insert x (y:ys)

| x <= y = x:y:ys| otherwise = y:insert x ys

Geoff Hamilton (DCU) CA320: Computability and Complexity 79 / 106

Functional Programming List Functions

List Functions

Design another function that sorts a list of values into ascendingorder.qsort :: Ord a => [a] -> [a]

For example: qsort [7,3,9,2] ⇒ [2,3,7,9]

As in all previous examples, our first intuition should be the twoinductive cases:qsort [] = ...qsort (x:xs) = ...

The first case is easy since the empty list is trivially sorted:qsort [] = []

Geoff Hamilton (DCU) CA320: Computability and Complexity 80 / 106

Functional Programming List Functions

List Functions

A sorted list is a list that has all the values smaller than (or equalto) the head of the list in front (sorted), followed by the head of thelist and then all the values that are bigger than the head (alsosorted).Since we need to further sort twice, there should be two recursivecalls.For the example above, we have x equal to 7, the first list to berecursively sorted equal to [3,2] and the second list to berecursively sorted equal to [9]

Hence we might expect the inductive case for qsort to look asfollows:qsort (x:xs) =

let smaller = [a | a <- xs, a <= x]bigger = [a | a <- xs, a > x]

in (qsort smaller) ++ [x] ++ (qsort bigger)

Geoff Hamilton (DCU) CA320: Computability and Complexity 81 / 106

Functional Programming User-Defined Types

Type Synonyms

Haskell allows us to give our own names to types we haveconstructed.For example, we may represent a quadratic ax2 +bx +c as atriple (Float,Float,Float)

Scripts can be made more readable by defining a moremeaningful name for this type:type Quadratic = (Float,Float,Float)

Similarly, dates like 12/3/1977 can be represented as triples(Int,Int,Int) so we may define a type synonym:type Date = (Int,Int,Int)

Geoff Hamilton (DCU) CA320: Computability and Complexity 82 / 106

Functional Programming User-Defined Types

User-Defined Types

So far we have only used the predefined and constructed types ofHaskell.For representing ‘realworld’ data types, we can do a lot with justthose types, possibly renaming them with synonyms.However, some ‘realworld’ data types do not naturally correspondto standard types and would require some kind of coding.For example, to represent the months of the year or the days ofthe week we could use the integers 1–12 and 1–7 respectively.In that case, it is not clear whether a literal 3 represents March,Tuesday, Wednesday, the value 3, ...

Geoff Hamilton (DCU) CA320: Computability and Complexity 83 / 106

Functional Programming User-Defined Types

User-Defined Types

Other examples where data type ‘coding’ is inconvenient, unclearand unnatural:

A type which is either a number or a string (for example, in someareas houses may have names rather than numbers).A tree data structure:

“Laois”

“Roscommon”

“Wexford”“Mayo”

“Dublin”

“Kerry”“Cork”

Any type can be represented using the predefined types, but therepresentation may not be very natural.Haskell’s user-defined types or algebraic types let us define newtypes to more naturally model types like those above.

Geoff Hamilton (DCU) CA320: Computability and Complexity 84 / 106

Functional Programming User-Defined Types

User-Defined Types

User-defined types can be defined through the use of datatypedeclarations, which have the following format:data Typename = Con1 t11 . . . t1k1

| . . .| Conn tn1 . . . tnkn

Constructors Con1 . . . Conn must begin with a capital letter (todistinguish them from variables). Remember that type names alsobegin with a capital letter.These can be used to create the following types:

enumerated typescomposite typesrecursive typesparametric types

Geoff Hamilton (DCU) CA320: Computability and Complexity 85 / 106

Functional Programming User-Defined Types

Enumerated Types

A datatype which consists of a finite number of constants is calledan enumerated type.For example, the months of the year and the days of the weekcould be defined as follows:data Day =

Sun | Mon | Tue | Wed | Thu | Fri | Satdata Month =

Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec

Another example:data Temp = Cold | Hotdata Season = Spring | Summer | Autumn | Winter

These are all new types. Type Temp consists of only two values:Cold and Hot.Cold and Hot are called constructors of type Temp.

Geoff Hamilton (DCU) CA320: Computability and Complexity 86 / 106

Functional Programming User-Defined Types

Enumerated Types

Constructors may appear in patterns.This is the standard way to define functions over algebraic types.For example:weather :: Season -> Tempweather Summer = Hotweather _ = Cold

Algebraic types do not automatically have operators such asequality, ordering, show and so on.For example, consider the following definition:weather s| s == Summer = Hot| otherwise = Cold

This gives the following error message:No instance for (Eq Season) arising from a use of ’==’

Geoff Hamilton (DCU) CA320: Computability and Complexity 87 / 106

Functional Programming User-Defined Types

Enumerated Types

If you need some overloaded operator such as (==) to apply toan algebraic type, you can declare it as an instance of class Eqand define the equality function yourself:instance Eq Temp whereHot == Hot = TrueCold == Cold = True_ == _ = False

This can get tedious and longwinded. (Try defining Month as aninstance of Ord.)Haskell has a standard way of deriving instances for algebraictypes.For example:data Season = Spring|Summer|Autumn|Winter

deriving Eq

Geoff Hamilton (DCU) CA320: Computability and Complexity 88 / 106

Functional Programming User-Defined Types

Enumerated Types

If we want to show the new values as strings, we also need toderive an instance of class Show.Otherwise:> weather Autumn

This gives the following error message:No instance for (Show Temp) arising from a use of ‘print’

We need:data Season = Spring|Summer|Autumn|Winter

deriving (Eq, Show)data Temp = Cold | Hot

deriving (Eq, Show)

Often we also want to derive an instance of classes Ord andEnum so that we can use the relational operators andprogressions, such as [Mon .. Fri].

Geoff Hamilton (DCU) CA320: Computability and Complexity 89 / 106

Functional Programming User-Defined Types

Composite Types

A program may be concerned with various geometrical shapes.Using an enumerated type we can distinguish between differentkinds of shapes, but we cannot carry other information such asdimensions.The alternatives in a data definition can include other types,rather than being simple constants like Hot and Cold:data Shape =

Circle Float | Rectangle Float Floatderiving (Eq, Show)

Now Circle is a constructor function.> :t CircleCircle :: Float -> Shape

In other words, Circle constructs a Shape from any Floatvalue.

Geoff Hamilton (DCU) CA320: Computability and Complexity 90 / 106

Functional Programming User-Defined Types

Composite Types

Constructor functions are the only functions that can appear inpatterns.For example:isRound :: Shape -> Bool

isRound (Circle r) = TrueisRound (Rectangle l b) = False

area :: Shape -> Float

area (Circle r) = pi * rˆ2area (Rectangle l b) = l * b

Geoff Hamilton (DCU) CA320: Computability and Complexity 91 / 106

Functional Programming User-Defined Types

Recursive Types

It is possible to use the algebraic type being defined in a datadefinition within its own definition.This means the type itself is recursive.Lists are a common example of a recursive type.A list is either empty, or it consists of a head and a tail where thetail is also a list.An algebraic type declaration for lists of integers:data List = Empty | Cons Int List

Another standard example is the tree structure shown at thebeginning of this section.A binary tree is either empty, or it consists of a node containingsome value and left and right subtrees where the subtrees arealso binary trees.An example where the node values are strings:data Tree = Null | Node String Tree Tree

Geoff Hamilton (DCU) CA320: Computability and Complexity 92 / 106

Functional Programming User-Defined Types

Recursive Types

Pattern matching can also be performed on recursive types. Forexample:

len :: List -> Intlen Empty = 0len (Cons x xs) = 1 + len xs

> len (Cons 3 (Cons 2 (Cons 1 Empty)))3

size :: Tree -> Intsize Null = 0size (Node x xt yt) = 1 + (size xt) + (size yt)

> size (Node "Laois" (Node "Dublin" Null Null) Null)2

Geoff Hamilton (DCU) CA320: Computability and Complexity 93 / 106

Functional Programming User-Defined Types

Parametric Types

Type definitions can be parameterised by type variables, thusallowing the definition of parametric or polymorphic types.The last two examples are constrained in the sense that typeList represents only lists of integers and Tree only representsbinary trees of strings.It is possible to define lists and trees of any component type asfollows:data List a = Empty | Cons a (List a)

data Tree a = Null | Node a (Tree a) (Tree a)

Now a list of integers is the type List Int and a tree of strings isTree String.Pattern matching can be performed on these parametric types justas for recursive types.

Geoff Hamilton (DCU) CA320: Computability and Complexity 94 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

In imperative programming languages there is a clear distinctionbetween functions and ordinary values.In functional languages this is not the case, and we can treatfunctions like any other expression, in particular we can use themas parameters and return values.No special syntax is needed - function parameters look the sameas any other kind of parameter:doTwice f x = f (f x)> doTwice (\x -> x+1) 57

(\x -> x+1) is an example of a lambda expression oranonymous function.

Geoff Hamilton (DCU) CA320: Computability and Complexity 95 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

We often want to transform each element of a list in some way. Asimple example is to double each element.We might write the doubling function like this:double :: [Int] -> [Int]double [] = []double (x:xs) = (x*2) : (double xs)

A function to add 1 to every element:incr :: [Int] -> [Int]incr [] = []incr (x:xs) = (x+1) : (incr xs)

Notice that the form of the definition is exactly the same. The onlydifference is the way in which the elements are transformed.

Geoff Hamilton (DCU) CA320: Computability and Complexity 96 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

Obviously any other function could be used in place of (*2) or(+1).Why not make those functions into arguments of a (higher-order)function representing the form of the definition?Such a mapping function will take two arguments:

a function to transform the elementsa list

This is exactly the function map defined in the prelude:map :: (a -> b) -> [a] -> [b]map f [] = []map f (x:xs) = (f x) : (map f xs)

Notice that map is polymorphic.

Geoff Hamilton (DCU) CA320: Computability and Complexity 97 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

Now we can define double and incr as follows:double xs = map (*2) xsincr xs = map (+1) xs

(*2) and (+1) are examples of sections.Or even:double = map (*2)incr = map (+1)

Sample trace:map (*2) [3, 9]

=> 3*2 : map (*2) [9]=> 3*2 : 9*2 : map (*2) []=> 3*2 : 9*2 : []=> [6, 18]

Geoff Hamilton (DCU) CA320: Computability and Complexity 98 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

What are the advantages of this approach?The map function is a good example of abstraction andgeneralisation.If you understand what map does, definitions that use it are easierto understand and modify.Code reuse is a cornerstone of modern software engineeringpractices.map can be reused for all functions of this form.

Geoff Hamilton (DCU) CA320: Computability and Complexity 99 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

We often want to combine all the elements of a list into a singlevalue in a uniform way.For example, the standard function sum adds together all theelements of a list:sum [] = 0sum (x:xs) = x + sum xs

The standard function product multiplies together all theelements of a list:product [] = 1product (x:xs) = x * product xs

concat combines a list of lists by the operator (++):concat [] = []concat (xs:xss) = xs ++ concat xss

The fold higher-order functions are not so straightforward asmap (the prelude contains five different versions).

Geoff Hamilton (DCU) CA320: Computability and Complexity 100 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

The function foldr (fold right) is defined as follows:foldr f z [] = zfoldr f z (x:xs) = f x (foldr f z xs)

The function foldl (fold left) is defined as follows:foldl f z [] = zfoldl f z (x:xs) = foldl f (f z x) xs

Here are definitions of sum, product and concat:sum = foldl (+) 0product = foldl (*) 1concat = foldr (++) []

Geoff Hamilton (DCU) CA320: Computability and Complexity 101 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

Some sample traces:

foldl (+) 0 [1,2,3]=> foldl (+) (0+1) [2,3]=> foldl (+) (0+1+2) [3]=> foldl (+) (0+1+2+3) []=> 0+1+2+3=> 6

foldr (++) [] [[1,2,3],[4,5],[6]]=> [1,2,3] ++ (foldr (++) [] [[4,5],[6]])=> [1,2,3] ++ [4,5] ++ (foldr (++) [] [[6]])=> [1,2,3] ++ [4,5] ++ [6] ++ (foldr (++) [] [])=> [1,2,3] ++ [4,5] ++ [6] ++ []=> [1,2,3,4,5,6]

Geoff Hamilton (DCU) CA320: Computability and Complexity 102 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

We often want to produce a ‘sublist’ by selecting the elements ofsome other list that all have some property in common.For example, we may wish to filter out the digits in a string:getDigits :: String -> Stringgetdigits [] = []getDigits (x:xs)

| isDigit x = x : getDigits xs| otherwise = getDigits xs

Now if we want just the negative integers, the function looks thesame except that x < 0 replaces isDigit x:getNegs :: [Int] -> [Int]getNegs [] = []getNegs (x:xs)

| x < 0 = x : getNegs xs| otherwise = getNegs xs

Geoff Hamilton (DCU) CA320: Computability and Complexity 103 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

This common form is known as filtering. There is a standardhigher-order function filter that takes a predicatep :: a -> Bool:filter :: (a -> Bool) -> [a] -> [a]filter p [] = []filter p (x:xs)

| p x = x : filter p xs| otherwise = filter p xs

Now we can define getDigits easily:getDigits xs = filter isDigit xs

Or even:getDigits = filter isDigit

We can similarly define getNegs using filter:getNegs = filter (<0)

Geoff Hamilton (DCU) CA320: Computability and Complexity 104 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

The built-in function takeWhile is defined as follows:

takeWhile p [] = []takeWhile p (x:xs) = if (p x) then x:(takeWhile p xs)

else []

This function takes elements from a given list while they satisfy a givenpredicate p. For example:

beforeThree = takewhile (<3)

> beforeThree [1,2,3,4][1,2]

The function beforeThree takes elements from the given list whilethey are less than 3.

Geoff Hamilton (DCU) CA320: Computability and Complexity 105 / 106

Functional Programming Higher-Order Functions

Higher-Order Functions

The built-in function dropWhile is defined as follows:

dropWhile p (x:xs) = if (p x) then dropWhile p xselse x:xs

This function drops elements from a given list while they satisfy a givenpredicate p. For example:

afterThree = dropWhile (<3)

> afterThree [1,2,3,4][3,4]

The function afterThree drops elements from the given list whilethey are less than 3.

Geoff Hamilton (DCU) CA320: Computability and Complexity 106 / 106