3.1 introduction - computing.dcu.iehamilton/teaching/ca320/notes/functional.pdf · prelude. useful...

43
CA320: COMPUTABILITY AND COMPLEXITY 1 3 Functional Programming 3.1 Introduction Programming Paradigms Two major programming paradigms: Imperative programming: Instructions are used to change the computer’s state, e.g. x:=x+1 Order of evaluation is given by sequencing of instructions Summing 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 expressions Summing the integers 1 to 10: sum [1..10] Functional Programming Some properties of functional programming languages: allow the programmer to take a high-level view of what is to be computed rather than how programs are built out of expressions (whose meanings are values) there is no notion of a state (i.e. no variables or assignment) there is no sequencing functions do not have any side effects (any effect other than to return the value of the function) Functional Programming When we remove updateable variables from a programming language, there are a few other casualties. In particular, the iterative constructs like while and for loops no longer make sense, since we cannot change the variables that might appear in the conditions that control the iteration. Geoff Hamilton

Upload: nguyenhuong

Post on 08-Jun-2018

216 views

Category:

Documents


0 download

TRANSCRIPT

CA320: COMPUTABILITY AND COMPLEXITY 1

3 Functional Programming

3.1 IntroductionProgramming ParadigmsTwo major programming paradigms:Imperative programming:

• Instructions are used to change the computer’s state, e.g. x:=x+1

• Order of evaluation is given by sequencing of instructions

• Summing 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 expressions

• Summing the integers 1 to 10: sum [1..10]

Functional ProgrammingSome properties of functional programming languages:

• allow the programmer to take a high-level view of what is to be computed ratherthan how

• programs are built out of expressions (whose meanings are values)

• there is no notion of a state (i.e. no variables or assignment)

• there is no sequencing

• functions do not have any side effects (any effect other than to return the valueof the function)

Functional Programming

• When we remove updateable variables from a programming language, there area few other casualties.

• In particular, the iterative constructs like while and for loops no longer makesense, since we cannot change the variables that might appear in the conditionsthat control the iteration.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 2

• Hence the only way to implement iteration in a functional language is throughthe 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;

}

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 any effect on the rest of our program.

• This property is known as referential transparency

• Basically, if we take an expression anywhere in a program, and replace it withanother expression that gives the same value, we will not have changed the over-all result of the program.

Functional Programming: Why?

• At first sight a language without variables, assignment and sequencing looks veryimpractical.

• We will show in these lectures how a lot of interesting programming can be donein the functional style.

• Imperative programming languages have arisen as an abstraction of hardware,from machine code, through assemblers and macro assemblers, to C, Java andbeyond.

• Perhaps this is the wrong approach and we should approach the task by mod-elling the problem rather than the solution.

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

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 3

• Not if you are using an imperative language!

• These equalities all hold for functional languages because functions cannot haveside-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 good programming prac-tice.

Disadvantages

• Functional programming is not without its deficiencies.

• Some things are harder to fit into a purely functional model:

– Input-output

– Interactive or continuously running programs (e.g. editors, process con-trollers)

• Functional languages also correspond less closely to current hardware, so theycan be less efficient, and it can be hard to reason about their time and spaceusage.

Functional Programming in the Real WorldSome examples of functional programming in the real world:

• Google: MapReduce, Sawzall

• Ericsson: AXE phone switch

• Facebook: Sigma spam-killer and Infer static analyser

• Perl 6

• DARCS

• XMonad

• Yahoo

• Twitter

• Garbage collection

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 4

The Rise of Functional ProgrammingErlang, F# and Scala are attracting a lot of interest from developers.Features from functional languages are also appearing in other languages:

• Garbage collection: Java, C#, Python, Perl, Ruby, Javascript

• Higher-order functions: Java, C#, Python, Perl, Ruby, Javascript

• Generics: Java, C#

• List comprehensions: C#, Python, Perl 6, Javascript

• Type classes: C++ ‘concepts’

3.2 GHCiHaskellThe Haskell programming language was designed by Simon Peyton-Jones et al and isnamed after Haskell B. Curry who did pioneering work on combinatory logic (similarto the λ -calculus, the mathematical theory on which functional programming is based).Haskell is:

• (almost) a purely functional language with no side-effects and referential trans-parency (with the exception of input/output);

• a lazy programming language, in that it does not evaluate an expression unless itreally has to;

• a statically typed language so that at compile time we know the type of eachexpression, and has a type inference system that tries to work out the type of anexpression if it has not been explicitly specified.

Glasgow Haskell Compiler (GHC)

• GHC is the leading implementation of Haskell, and comprises a compiler andinterpreter.

• The interactive nature of the interpreter GHCi makes it well suited for teachingand prototyping.

• GHC is freely available from: www.haskell.org/platform.

• The interpreter can be started from the terminal command prompt $ by simplytyping ghci:

$ ghci

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

>

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

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 5

GHCiGHCi can be used as a desktop calculator to evaluate simple numeric expresions:

> 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 includedin expressions. They are defined in the Prelude which is just a Haskell program.

GHCi

• As well as the functions in the Prelude, you can also define your own functions.

• New functions are defined within a script, a text file comprising a sequence ofdefinitions.

• By convention, Haskell scripts usually have a .hs suffix on their filename.

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

• When developing a Haskell script, it is useful to keep two windows open, onerunning an editor for the script, and the other running GHCi.

• By loading a script into GHCi, you can use the session to evaluate expressionscontaining functions and operators defined in the script, as well as those in thePrelude.

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

CA320: COMPUTABILITY AND COMPLEXITY 6

Script Contents

• Comments

-- square the value given by x

is a single-line comment. Comments start with -- and end with the end of theline.

{-square the value given by x

-}

is a multi-line comment. Comments start with {- and end with -}.

• Type Declarations

square :: Int -> Int

is a type declaration. It tells GHCi (and human readers) the type of square.

• Definitions

square x = x * x

is a definition. It defines the function square.

Identifiers

• Function and argument identifiers must begin with a lower case letter.

• This can be followed by any sequence of letters, digits, underscores ( ) andprimes (′).

• Examples: myFun, fun1, arg 2, x’

• By convention, list arguments usually have an s suffix on their name. For exam-ple: xs, ns, xss

• You cannot use a name that has already been used; this includes all the functionsin the prelude. For example: sqrt, head, tail, length

• Some names are reserved words, and have a special meaning in Haskell. Youcannot use them for any other purpose. For example: if, then, else, case

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 7

3.3 TypesTypes

• In Haskell, every well formed expression has a type, which can be automaticallycalculated at compile time using a process called type inference.

• All type errors are found at compile time, which makes programs safer and fasterby removing the need for type checks at run time.

• Values can be of a basic type such as integer, boolean etc. or a compound 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

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 certain types. For exam-ple the expression square ’A’ would give a type error.

Numbers

• Numbers in Haskell can be integers (of type Int or Integer) or floats (of typeFloat).

• Floats must have a fractional part (e.g. 3.0) or an exponential part (e.g. 3E7), orboth (e.g. 3.0E7).

• The following infix operators for numbers are built-in (the normal precedencesapply):

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

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 8

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

Booleans

• Booleans (of type Bool) can have the values True or False.

• The following operators for booleans are built-in (the normal precedences ap-ply):

– && logical and

– || logical or

– not logical negation

• The relational operators which can be used to give a boolean result are ==, /=,>, >=, <, <=

• Equality can be tested for most types (except functions).

Examples

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

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 9

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

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 gives the string backas 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 function putStr asfollows:

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

Functions

• Functions have types of the form f :: t1 -> tn -> t

• Here, the function f has n arguments of types t1 . . . tn respectively, and returns aresult of type t.

• For example:

add :: Int -> Int -> Intadd x y = x+y

• It is also legal to write an expression like add 3.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 10

• The value of the expression add 3 is a function of type Int -> Int whichwill add 3 to its argument.

• So add 3 4 is the same as (add 3) 4.

• In Haskell, all functions behave this way. They are called curried functions afterthe American logician Haskell B. Curry.

• add 3 4+5 means (add 3 4)+5, not add 3 (4+5).

• Function application is just like any other operator, but it has higher priority thanall other operators and it is left associative.

Built-In FunctionsSome 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

Polymorphic Functions

• A function is called polymorphic (“of many forms”) if its type contains one ormore type variables.

• For example:

identity x = x

Has the following type:

identity :: t -> t

• For any type t, identity takes a value of type t and returns a value of thesame type.

• Type variables can be instantiated to different types in different circumstances:

> identity FalseFalse> identity 44

• Type variables must begin with a lower-case letter, e.g., a, t, etc.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 11

Overloaded Functions

• A polymorphic function is called overloaded if its type contains one or moreclass constraints:

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

• For any numeric type a, (+) takes two values of type a and returns a value oftype a.

• Constrained type variables can be instantiated to any types that satisfy the con-straints:

> 1 + 23> 1.0 + 2.03.0> ’a’ + ’b’ERROR

Type ClassesHaskell has a number of type classes, including:

• Num - numeric types. The Num class includes all types that act as numbers.

• Eq - equality types. The Eq class includes all standard Haskell types except IOand functions.

• Ord - ordered types. Most Haskell standard types belong to Ord except functiontypes and some abstract types.

• Show - types which can be displayed as strings. Most Haskell standard typesbelong 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

3.4 ListsLists

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

• An empty list is represented by []

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 12

• The infix operator : (cons) makes a list by putting an element in front of anexisting list. For example:

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

Lists

• Every list is either empty or has the form h:t, where h is called the head of thelist and t is called the tail.

• The built-in functions head and tail can be used to extract the head and tailrespectively 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

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:

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 13

> length [2,4,6,8]4

• null: indicates whether or not a list is empty:

> null [2,4,6,8]False

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 a list:

> take 2 [2,4,6,8][2,4]

• drop: drops the specified number of elements from the start of a list:

> drop 3 [2,4,6,8][8]

• maximum: returns the largest element of a list:

> maximum [2,8,4,9,6]9

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

CA320: COMPUTABILITY AND COMPLEXITY 14

List Ranges

• Haskell provides a special ‘ellipsis’ or ‘dot-dot’ syntax for list ranges. For ex-ample:

[1..5] == [1,2,3,4,5]

• To specify step sizes other than 1, give the first 2 numbers. For example:

[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, ...

List Comprehensions

• List comprehensions are a convenient way to define lists where the elementsmust all satisfy certain properties (similar to set comprehensions).

• For example, the squares of all the values from 1 to 10 which are even is definedas follows:

[xˆ2 | x <- [1..10], even x]

• x <- [1..10] is the generator and even x is the guard

• With two generators, we get all possible pairs of values:

> [(x,y) | x <- [1,2,3], y <- [’a’,’b’]][(1,’a’),(1,’b’),(2,’a’),(2,’b’),(3,’a’),(3,’b’)]

Changing the order of the generators changes the order of the elements in thefinal list:

> [(x,y) | y <- [’a’,’b’], x <- [1,2,3]][(1,’a’),(2,’a’),(3,’a’),(1,’b’),(2,’b’),(3,’b’)]

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 15

List Comprehensions

• Later generators can depend on the variables that are introduced by earlier gen-erators:

> [(x,y) | x <- [1..3], y <- [x..3]][(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]

• Using a dependent generator we can define our own version of the library func-tion that concatenates a list of lists:

myConcat :: [[a]] -> [a]

myConcat xss = [x | xs <- xss, x <- xs]

> myConcat [[1,2,3],[4,5],[6]][1,2,3,4,5,6]

List Comprehensions

• Using a guard we can define a function that maps a positive integer to its list offactors:

factors :: Int -> [Int]

factors n = [x | x <- [1..n], n ‘mod‘ x == 0]

• Using factors we can define a function that decides if a number is prime:

prime :: Int -> Bool

prime n = factors n == [1,n]

• Using a guard we can now define a function that returns the list of all primes upto a given limit:

primes :: Int -> [Int]

primes n = [x | x <- [2..n], prime x]

3.5 TuplesTuples

• A tuple is a finite sequence of elements which can have different types. Forexample, (1,"Smith",True) has type (Int,String,Bool)

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 16

• A tuple type depends on its size, the types of its components and the order inwhich they occur.

• The tuples (1,"world") and ("world",1) have different types since thefirst tuple has the type (Int,String) whereas the second tuple has the type(String,Int)

• A pair is the smallest tuple and there are standard functions for extracting thefirst and second elements of a pair:

> fst(1,"world")1> snd(1,"world")"world"

Tuples

• We can use a tuple of three integers to represent the lengths of the sides of atriangle.

• We can generate a list of triangles whose perimeter is at most 24 as follows:

[(a,b,c) | a <-[1..24],b<-[1..24],c<-[1..24],a+b+c<= 24]

• We can remove duplicate triangles such as (10,12,2), (12,10,2) and (2,12,10) byrequiring that the sides are in descending order:

[(a,b,c) | a <-[1..24],b<-[1..a],c<-[1..b],a+b+c<= 24]

• We can also determine how many of these triangles are right-angled triangles:

length [(a,b,c) | a <-[1..24],b<-[1..a],c<-[1..b],a+b+c <= 24, aˆ2 == bˆ2 + cˆ2]

3.6 FunctionsExpression Evaluation

• Haskell evaluates an expression by repeatedly performing simplifications (or re-ductions) on the expression until no more reductions can be performed.

• When an expression is fully reduced in this way it is said to be in normal form.

• Examples of the different kinds of reduction are:

– replacing a built-in function application with its value

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 17

– replacing a user-defined function name with its body and substituting theactual parameters into it to replace the corresponding identifiers

• For example:

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

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 a definition, theevaluation mechanism simply replaces it by the right-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 sides

– patterns and alternative left hand sides

– local definitions (i.e. where clauses)

Recursion

• Many functions can naturally be defined in terms of other functions. For exam-ple:

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

CA320: COMPUTABILITY AND COMPLEXITY 18

Recursion

• In Haskell, functions can also be defined in terms of themselves. Such functionsare 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

Recursion

• This function uses a conditional expression. In Haskell, these must always havean else branch.

• To prevent an infinite loop, a recursive function must have a base case.

• The base case for this function is when the parameter n is equal to 0.

• There must also be an inductive case in which the parameters of the recursivefunction call move closer to the base case.

• In the inductive case for this function, the parameter is reduced by one movingit towards the base base.

• This function can still have an infinite loop for negative integers because the basecase is never reached.

Guards

• As an alternative to conditionals, functions can also be defined using guardedequations:

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 of them.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 19

• GHC evaluates each guard in turn, first to last, until it finds one that equals True.

• The left-hand side is then replaced by the right hand side that corresponds to thattrue 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.

Guards

• Haskell provides a special guard otherwise that is always true.

• otherwise is used as a catch-all guard at the end of a sequence of 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 atthe end of the match?

points :: Int -> Int -> Intpoints for against

| for > against = 3| for == against = 1| otherwise = 0

Pattern Matching

• Function definitions can consist of a number of separate clauses with patterns onthe 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 make definitions morereadable.

• It is important to realise that patterns are not arbitrary expressions. For example,this is illegal:

squareRoot (x*x) = x

• This does not tell how to compute the square root function; patterns and expres-sions are not the same thing.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 20

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 next pattern :xs

– The list is matched against :xs, which is a list with something at its 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 care about

the value we are matching against.

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 first element of the pairand y to be the second element.

• Example: (0.0, a) matches any pair where the first element is the Floatvalue 0.0. The second element of the pair is bound to a.

• Example: (0, (’5’, answer)) matches any pair whose first element is 0and whose second element is a pair whose first element is the character ’5’.The second element of the second element is bound to answer.

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 applies inside the def-inition of points; this is why it is called a local definition.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 21

Where Clauses

• Pattern matching can also be performed in where clauses. For example:

initials :: String -> String -> Stringinitials xs ys = [x] ++ ". " ++ [y] ++ "."

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

• Local functions can also be defined in where clauses. For example:

initials :: String -> String -> Stringinitials xs ys = ini xs ++ ". " ++ ini ys ++ "."

where ini (x:_) = x

Let Expressionslet 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 used across multipleclauses in a function definition.

• let expressions can be used anywhere other expressions can be used, so we canwrite expressions such as:

4 * (let a = 9 in a + 1) + 2

Case Expressionscase expressions are similar to case statements from imperative languages but withpattern matching added:

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

(x:_) -> x

Whereas pattern matching on function parameters can only be done when definingfunctions, case expressions can be used anywhere other expressions can be used. Forexample:

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

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

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 22

3.7 LayoutLayout

• In any programming language, the layout of your program is important for thereadability of your programs.

• In Haskell, layout rules help to get rid of the annoying punctuation used in manyother 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 appears in the samecolumn as the first symbol of the definition.

• Here are some examples:

myMin x y|x<y=x|otherwise=y

This is correct, but ugly and hard to read.

myMin x y| x < y = x| otherwise = y

This is better.

Layout

cube x= x *

x * xanswer = 6 * 7

This is correct. The definition of cube ends when the ‘a’ of answer appears in thesame column as the ‘c’ of cube.

cube x= x *

x * x

This is wrong. The definition of cube is ended by (before) the equal sign. GHC willdetect this as a syntax error:

ERROR "cube.hs":2:1: parse error on input ’=’

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 23

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 thismust hold even if sections of code are separated by comments.

LayoutYou should adopt the following layout of function definitions as standard:

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

...| guard_k = e_k

wherelocal_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 a uniform style.

3.8 Type CheckingType Checking

• Strongly typed languages such as Java give programmers security by restrictingtheir freedom to make mistakes.

• Weakly typed languages such as Prolog allow programmers more freedom andflexibility with a reduced amount of security.

• The Haskell type system gives the security of strong type checking as well asgreater flexibility.

• Every expression must have a valid type, but it is not necessary for the program-mer to include type information

– this is inferred automatically.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 24

Type Checking

• Given little or no explicit type information, Haskell can infer all the types asso-ciated with a function definition. For example:

– the types of constants can be inferred automatically

– identifiers must have the same type everywhere within an expression

• 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.

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

CA320: COMPUTABILITY AND COMPLEXITY 25

Type Checking

• Type inference leaves some types completely unconstrained; in such cases thedefinition is polymorphic (this is parametric polymorphism).

• 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 of monotypes whichcan be obtained by instantiating the type variables with monotypes (e.g. [Int],[Bool]).

Type Checking

• GHC can work out the type of any expression and any function from its defini-tion.

• Even if you declare the type of a function, GHC will work it out anyway andcheck whether you are right.

• You should always declare the type of any functions you are defining in yourprograms:

– The type of a function is a basic part of its design. How can you be clearabout a function’s behaviour unless you at least know the types of its argu-ments 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 you expect, thenyou have made an error.

Type Checking

• For example, suppose you wish to design a function myEven such that myEven xreturns True if x is divisible by 2 and False otherwise as follows:

myEven :: Int -> BoolmyEven x = x ‘div‘ 2

• When loading a script containing this definition, GHCi will give an error mes-sage:

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 26

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 myEven has a type thatis different from its declaration.

• If we had not declared its type, a less obvious error message would occur whenwe used myEven.

3.9 List FunctionsList Functions

• Suppose we wanted to define a function to add together all the elements of a listof integers. (There is a standard function sum that does this, but ignore that forthe 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 a head

(element) and a tail (list): (x:xs).

List Functions

• Design of mySum:

– The sum of the empty list is 0– The 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

CA320: COMPUTABILITY AND COMPLEXITY 27

List Functions

• The inductive design of mySum translates directly into the following Haskelldefinition.

mySum [] = 0mySum (x:xs) = x + mySum xs

• Notice how patterns are used to distinguish the empty and non-empty cases.

• Notice how the second clause defines mySum in terms of mySum applied to a“smaller” argument.

• The argument in the recursive calls get smaller and smaller until it is the emptylist. So we escape the recursion through the first clause (the base case).

• Notice how the definition is like mathematical induction: the base case is listsof length 0 and the inductive case defines the function for lists of length n+1 interms of the result for lists of length n.

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 onto doubleof the tail of the list.

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

• Example: evaluate the following:

double [1,9,37]

List Functions

• Example: length is a standard function but how would we define it ourselves?

• Design:

– (base case) the length of [] is 0.

– (inductive case) a non-empty list is 1 element longer than its tail.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 28

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

• Example: evaluate the following:

myLength [1,9,37]

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, the combinations ofbase 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 redundancies and eliminatethem.

List Functions

• The first two cases do not need to be separated. The following clause coversboth:

append [] ys = ys

• It is less obvious, but the third and fourth cases can also be combined:

append (x:xs) ys = x:(append xs ys)

• So in fact, recursion on just the first list is sufficient to define this particularfunction.

• Attempting to define append by induction on only the second list doesn’t seempossible:

append xs [] = xsappend xs (y:ys) = ?

• The problem is that the basic list constructor (:) adds elements to the front of alist. The pattern (y:ys) binds y to some element that will end up in the middleof the result list...

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 29

List Functions

• Pattern-matching is a readable and powerful way of defining list functions inHaskell.

• 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 than expecting the pattern-matching to do it for you:

find barcode ((code,name,price):rest)| barcode == code = (name,price)| ...

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 thesimplest and clearest definition of the function.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 30

List Functions

• Haskell attempts to match patterns in a strictly sequential order:

– left to right in a definition; and

– top to bottom in a sequence of definition clauses.

• Note that as soon as a pattern match fails, that equation is rejected and the nextone 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 that clause isrejected without attempting to match the second argument. myZip truncates thelonger of the two list arguments.

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]

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

CA320: COMPUTABILITY AND COMPLEXITY 31

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 two inductive cases:

isort [] = ...isort (x:xs) = ...

• The first case is easy since the empty list is trivially sorted:

isort [] = []

List Functions

• For non-empty lists we have direct access to the head x and the tail xs, e.g.:

7:[3,9,2]

• Typically, there is a recursive call of the function on the tail of the list.

• 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 xs equal to [2,3,9]

• To build the final result, isort (x:xs) from x and isort xs, we need toinsert 7 into the correct position in [2,3,9].

• Hence we can write:

isort (x:xs) = insert x (isort xs)

List Functions

• To define the function insert, our intuition should be to try the standard in-ductive approach:

insert :: Ord a => a -> [a] -> [a]insert x [] = ...insert x (y:ys) = ...

• To insert x into an empty list maintaining ascending ordering:

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 32

insert x [] = [x]

• If the list is non-empty, we have direct access to its head. We may ask: does xbelong 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 of insert tofind out where it belongs:

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

List Functions

• Design another function that sorts a list of values into ascending order.

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 two inductive cases:

qsort [] = ...qsort (x:xs) = ...

• The first case is easy since the empty list is trivially sorted:

qsort [] = []

List Functions

• A sorted list is a list that has all the values smaller than (or equal to) the head ofthe list in front (sorted), followed by the head of the list and then all the valuesthat are bigger than the head (also sorted).

• Since we need to further sort twice, there should be two recursive calls.

• For the example above, we have x equal to 7, the first list to be recursively sortedequal to [3,2] and the second list to be recursively sorted equal to [9]

• Hence we might expect the inductive case for qsort to look as follows:

qsort (x:xs) =let smaller = [a | a <- xs, a <= x]

bigger = [a | a <- xs, a > x]in (qsort smaller) ++ [x] ++ (qsort bigger)

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 33

3.10 User-Defined TypesType Synonyms

• Haskell allows us to give our own names to types we have constructed.

• For example, we may represent a quadratic ax2+bx+c as a triple (Float,Float,Float)

• Scripts can be made more readable by defining a more meaningful name for thistype:

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)

User-Defined Types

• So far we have only used the predefined and constructed types of Haskell.

• For representing ‘realworld’ data types, we can do a lot with just those types,possibly renaming them with synonyms.

• However, some ‘realworld’ data types do not naturally correspond to standardtypes and would require some kind of coding.

• For example, to represent the months of the year or the days of the week wecould use the integers 1–12 and 1–7 respectively.

• In that case, it is not clear whether a literal 3 represents March, Tuesday, Wednes-day, the value 3, ...

User-Defined Types

• Other examples where data type ‘coding’ is inconvenient, unclear and unnatural:

– A type which is either a number or a string (for example, in some areashouses 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 the representationmay not be very natural.

• Haskell’s user-defined types or algebraic types let us define new types to morenaturally model types like those above.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 34

User-Defined Types

• User-defined types can be defined through the use of datatype declarations, whichhave the following format:

data Typename = Con1 t11 . . . t1k1| . . .| Conn tn1 . . . tnkn

• Constructors Con1 . . . Conn must begin with a capital letter (to distinguish themfrom variables). Remember that type names also begin with a capital letter.

• These can be used to create the following types:

– enumerated types

– composite types

– recursive types

– parametric types

Enumerated Types

• A datatype which consists of a finite number of constants is called an enumeratedtype.

• For example, the months of the year and the days of the week could be definedas follows:

data Day =Sun | Mon | Tue | Wed | Thu | Fri | Sat

data 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 andHot.

• Cold and Hot are called constructors of type Temp.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 35

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 as equality, 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 ’==’

Enumerated Types

• If you need some overloaded operator such as (==) to apply to an algebraictype, you can declare it as an instance of class Eq and define the equality functionyourself:

instance Eq Temp whereHot == Hot = TrueCold == Cold = True_ == _ = False

• This can get tedious and longwinded. (Try defining Month as an instance ofOrd.)

• Haskell has a standard way of deriving instances for algebraic types.

• For example:

data Season = Spring|Summer|Autumn|Winterderiving Eq

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 36

Enumerated Types

• If we want to show the new values as strings, we also need to derive an instanceof 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|Winterderiving (Eq, Show)

data Temp = Cold | Hotderiving (Eq, Show)

• Often we also want to derive an instance of classes Ord and Enum so that wecan use the relational operators and progressions, such as [Mon .. Fri].

Composite Types

• A program may be concerned with various geometrical shapes.

• Using an enumerated type we can distinguish between different kinds of shapes,but we cannot carry other information such as dimensions.

• The alternatives in a data definition can include other types, rather than beingsimple 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 Float value.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 37

Composite Types

• Constructor functions are the only functions that can appear in patterns.

• 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

Recursive Types

• It is possible to use the algebraic type being defined in a data definition withinits 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 the tail is also alist.

• An algebraic type declaration for lists of integers:

data List = Empty | Cons Int List

• Another standard example is the tree structure shown at the beginning of thissection.

• A binary tree is either empty, or it consists of a node containing some value andleft and right subtrees where the subtrees are also binary trees.

• An example where the node values are strings:

data Tree = Null | Node String Tree Tree

Recursive TypesPattern matching can also be performed on recursive types. For example:

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 38

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

Parametric Types

• Type definitions can be parameterised by type variables, thus allowing the defi-nition of parametric or polymorphic types.

• The last two examples are constrained in the sense that type List representsonly lists of integers and Tree only represents binary trees of strings.

• It is possible to define lists and trees of any component type as follows:

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 is Tree String.

• Pattern matching can be performed on these parametric types just as for recursivetypes.

3.11 Higher-Order FunctionsHigher-Order Functions

• In imperative programming languages there is a clear distinction between func-tions and ordinary values.

• In functional languages this is not the case, and we can treat functions like anyother expression, in particular we can use them as parameters and return values.

• No special syntax is needed - function parameters look the same as any otherkind of parameter:

doTwice f x = f (f x)> doTwice (\x -> x+1) 57

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

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 39

Higher-Order Functions

• We often want to transform each element of a list in some way. A simple exampleis 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 only difference isthe way in which the elements are transformed.

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 rep-resenting the form of the definition?

• Such a mapping function will take two arguments:

– a function to transform the elements

– a 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.

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.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 40

• 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]

Higher-Order Functions

• What are the advantages of this approach?

• The map function is a good example of abstraction and generalisation.

• If you understand what map does, definitions that use it are easier to understandand modify.

• Code reuse is a cornerstone of modern software engineering practices.

• map can be reused for all functions of this form.

Higher-Order Functions

• We often want to combine all the elements of a list into a single value in a uniformway.

• For example, the standard function sum adds together all the elements of a list:

sum [] = 0sum (x:xs) = x + sum xs

• The standard function product multiplies together all the elements 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 as map (the preludecontains five different versions).

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 41

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 (++) []

Higher-Order FunctionsSome 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]

Higher-Order Functions

• We often want to produce a ‘sublist’ by selecting the elements of some other listthat 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

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 42

• Now if we want just the negative integers, the function looks the same exceptthat x < 0 replaces isDigit x:

getNegs :: [Int] -> [Int]getNegs [] = []getNegs (x:xs)

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

Higher-Order Functions

• This common form is known as filtering. There is a standard higher-order func-tion filter that takes a predicate p :: 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)

Higher-Order FunctionsThe 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 given predicate p.For example:

beforeThree = takewhile (<3)

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

The function beforeThree takes elements from the given list while they are lessthan 3.

Geoff Hamilton

CA320: COMPUTABILITY AND COMPLEXITY 43

Higher-Order FunctionsThe 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 given predicate p.For example:

afterThree = dropWhile (<3)

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

The function afterThree drops elements from the given list while they are less than3.

Geoff Hamilton