32
Real World Haskell: Lecture 2 Bryan O’Sullivan 2009-10-14

Real World Haskell: Lecture 2

Embed Size (px)

Citation preview

Page 1: Real World Haskell: Lecture 2

Real World Haskell:Lecture 2

Bryan O’Sullivan

2009-10-14

Page 2: Real World Haskell: Lecture 2

My homework, using only concepts from last week

import Data . L i s t ( i s I n f i x O f )

p a t t e r n = ” t o a s t ”

format number l i n e = show number ++ ” : ” ++ l i n e

gr ep number i n p u t= i f n u l l i n p u t

then [ ]e l s e i f i s I n f i x O f p a t t e r n ( head i n p u t )

then fo rmat number ( head i n p u t ): g re p ( number + 1) ( t a i l i n p u t )

e l s e gr ep ( number + 1) ( t a i l i n p u t )

grepFromOne i n p u t = un l i nes ( g r ep 1 ( l i n e s i n p u t ) )

main = i n t e r ac t grepFromOne

Page 3: Real World Haskell: Lecture 2

Wasn’t Haskell supposed to be “pretty”?

That grep function sure didn’t look pretty to me!

But what, specifically, is ugly about it?

I We repeat ourselves, using head and tail twice.

I There’s a mess of nested if /else badness going on.

Page 4: Real World Haskell: Lecture 2

Lists, revisited

There are two ways to construct a list:

I An empty list[]

I A non-empty listfirstElement : restOfList

We refer to [] and : as list constructors, since they construct listvalues.

Page 5: Real World Haskell: Lecture 2

Lists, constructed

Knowing about these constructors, how might we construct a4-element list?

I 1 : 2 : 3 : 4 : []

The bracketed notation we saw last week is syntactic sugar for theform above.

In other words, any time you see this:

I [1,2,3,4]

You can read it as this, and vice versa:

I 1 : 2 : 3 : 4 : []

Page 6: Real World Haskell: Lecture 2

Lists, constructed

Knowing about these constructors, how might we construct a4-element list?

I 1 : 2 : 3 : 4 : []

The bracketed notation we saw last week is syntactic sugar for theform above.

In other words, any time you see this:

I [1,2,3,4]

You can read it as this, and vice versa:

I 1 : 2 : 3 : 4 : []

Page 7: Real World Haskell: Lecture 2

Lists, misconstrued

Beginner mistake alert:A list must end with an empty list. So a construction like thismakes no sense:

I ’a’ : ’b’ : ’c’

How would we fix it up?

I ’a’ : ’b’ : ’c’ : []

Page 8: Real World Haskell: Lecture 2

Back to our roots

Remember the fragment of square root code from last week?

oneRoot a b c = (−b + ( bˆ2 + 4∗a∗c ) ) / (2∗ a )

If we pass in a value of zero for a, the root is undefined, since we’dbe dividing by zero.

oneRoot a b c = i f a == 0then (−b + ( bˆ2 − 4∗a∗c ) )

/ (2∗ a )e l s e e r ro r ” d i v i d e by z e r o ! ”

Page 9: Real World Haskell: Lecture 2

But...

I don’t like that if , because how would we write this usingmathematical notation?

roots(a, b, c) =−b ± (b2 − 4ac)

2aif a 6= 0

= undefined otherwise

And . . . isn’t Haskell supposed to be mathematically inspired?

Page 10: Real World Haskell: Lecture 2

Introducing guards

A guard is a Boolean expression preceded by a vertical barcharacter.

oneRoot a b c| a /= 0 = (−b + ( bˆ2 − 4∗a∗c ) ) / (2∗ a )| otherwise = e r ro r ” d i v i d e by z e r o ”

I Guards are evaluated in top-to-bottom order.

I For the first one that evaluates to True, the expression on theright of the = sign is used as the result of the function.

I The name otherwise is simply another name for True.

Page 11: Real World Haskell: Lecture 2

Using guards

Here’s a second attempt at our grep function, this time usingguarded expressions:

g r ep number i n p u t| nu l l i n p u t

= [ ]| i s I n f i x O f p a t t e r n ( head i n p u t )

= format number ( head i n p u t ): g re p ( number + 1) ( t a i l i n p u t )

| otherwise= gre p ( number + 1) ( t a i l i n p u t )

Page 12: Real World Haskell: Lecture 2

How did this help?

We got rid of the nested if expressions, and our “flatter” code iseasier to follow.

It’s still fugly and repetitive, though. What about head and tail ?

Page 13: Real World Haskell: Lecture 2

Pattern matching

When we construct a list, the Haskell runtime has to rememberwhat constructors we used.

It goes a step further, and makes this information available to us.

We can examine the structure of a piece of data at runtime usingpattern matching.

Page 14: Real World Haskell: Lecture 2

Pattern matching on an empty list

What’s the length of an empty list?

myLength [ ] = 0

This is a function of one argument.

If that argument matches the empty-list constructor, our functionreturns the value 0.

Page 15: Real World Haskell: Lecture 2

Pattern matching on a non-empty list

What’s the length of a non-empty list?

myLength ( x : xs ) = 1 + myLength xs

If our argument matches the non-empty-list constructor “:”, then:

I the head of the list is bound to the name x;

I the tail to xs;

I and the expression is returned with those bindings.

Page 16: Real World Haskell: Lecture 2

Aaaand it’s over to you

Now that we know how pattern matching works, let’s do somesuper-simple exercises:

Write versions of the head and tail functions:

head [ 1 , 2 , 3 ]==> 1

t a i l [ ’ a ’ , ’ b ’ , ’ c ’ ]==> [ ’ b ’ , ’ c ’ ]

Give your versions different names, or you’ll have a hard timetrying them out in ghci.

Page 17: Real World Haskell: Lecture 2

Matching alternative patterns

We combine our two pattern matches into one function definitionby writing them one after the other:

myLength [ ] = 0myLength ( x : xs ) = 1 + myLength xs

As with guards, pattern matching proceeds from top to bottomand stops at the first success.

I The RHS of the first pattern that succeeds is used as thebody of the function.

Question: What do you suppose happens if no pattern matches?

Page 18: Real World Haskell: Lecture 2

Matching alternative patterns

We combine our two pattern matches into one function definitionby writing them one after the other:

myLength [ ] = 0myLength ( x : xs ) = 1 + myLength xs

As with guards, pattern matching proceeds from top to bottomand stops at the first success.

I The RHS of the first pattern that succeeds is used as thebody of the function.

Question: What do you suppose happens if no pattern matches?

Page 19: Real World Haskell: Lecture 2

Over to you, part two

And now that we know how to write function definitions that candeal with multiple patterns, another exercise:

Write a version of the take function:

take 3 [ 1 0 0 , 2 0 0 , 3 0 0 , 4 0 0 , 5 0 0 ]==> [ 1 0 0 , 2 0 0 , 3 0 0 ]

take 3 [ ’ a ’ , ’ b ’ ]==> [ ’ a ’ , ’ b ’ ]

take 3 [ ]==> ???

Now use ghci to figure out what the drop function does, andwrite a version of that.

Page 20: Real World Haskell: Lecture 2

Over to you, part two

And now that we know how to write function definitions that candeal with multiple patterns, another exercise:

Write a version of the take function:

take 3 [ 1 0 0 , 2 0 0 , 3 0 0 , 4 0 0 , 5 0 0 ]==> [ 1 0 0 , 2 0 0 , 3 0 0 ]

take 3 [ ’ a ’ , ’ b ’ ]==> [ ’ a ’ , ’ b ’ ]

take 3 [ ]==> ???

Now use ghci to figure out what the drop function does, andwrite a version of that.

Page 21: Real World Haskell: Lecture 2

Metasyntactic variables

Languages have their cultural habits, and Haskell is no exception.

You’ll very often see the names used when pattern matching a listfollow a naming convention like this:

I (x:xs)

I (y:ys)

I (d:ds)

and so on.

Think of the “s” suffix as “pluralizing” a name, so “x” (ex) is thehead of the list, and “xs” (exes) is the rest.

Page 22: Real World Haskell: Lecture 2

Matching multiple patterns

We can match more than one pattern at a time.

Consider how we might add the elements of two vectors,represented as lists:

sumVec ( x : xs ) ( y : ys ) = x + y : sumVec xs yssumVec [ ] [ ] = [ ]

Page 23: Real World Haskell: Lecture 2

Combining pattern matching and guards

Things start to get seriously expressive when we combine languagefeatures.

Remember that bloated grep definition from earlier? Let’s put ournew friends to work!

g r ep n [ ] = [ ]g r ep n ( x : xs )| i s I n f i x O f p a t t e r n x = format n x

: g re p ( n+1) xs| otherwise = gre p ( n+1) xs

Page 24: Real World Haskell: Lecture 2

What’s happening here?

When we define a function, a pattern binds names to values. Givena list and a pattern (x:xs), if the list is non-empty, then x is boundto its head, and xs to its tail.

I Then each guard (if any) associated with that pattern isevaluated in turn, with those bindings in effect, until a guardsucceeds.

I Once a guard succeeds, its RHS is used as the result, with thebindings from that pattern still in effect.

I If the pattern match fails, or no guard succeeds, we fallthrough to the next pattern and its guards.

Note: If all patterns and guards in a function definition were to failon some input, we’d get a runtime error. That would be bad.

Page 25: Real World Haskell: Lecture 2

What’s happening here?

When we define a function, a pattern binds names to values. Givena list and a pattern (x:xs), if the list is non-empty, then x is boundto its head, and xs to its tail.

I Then each guard (if any) associated with that pattern isevaluated in turn, with those bindings in effect, until a guardsucceeds.

I Once a guard succeeds, its RHS is used as the result, with thebindings from that pattern still in effect.

I If the pattern match fails, or no guard succeeds, we fallthrough to the next pattern and its guards.

Note: If all patterns and guards in a function definition were to failon some input, we’d get a runtime error. That would be bad.

Page 26: Real World Haskell: Lecture 2

And speaking of bad. . .

Remember our sumVec function?

sumVec ( x : xs ) ( y : ys ) = x + y : sumVec xs yssumVec [ ] [ ] = [ ]

What happens if we apply this to lists of different lengths?

I sumVec [1,2,3] [4,5,6,7,8]

So . . . what can we do about that exciting behaviour?

Page 27: Real World Haskell: Lecture 2

And speaking of bad. . .

Remember our sumVec function?

sumVec ( x : xs ) ( y : ys ) = x + y : sumVec xs yssumVec [ ] [ ] = [ ]

What happens if we apply this to lists of different lengths?

I sumVec [1,2,3] [4,5,6,7,8]

So . . . what can we do about that exciting behaviour?

Page 28: Real World Haskell: Lecture 2

One possible response

Let’s declare that the sum of two vectors should end when wereach the end of the shorter vector.

sumVec ( x : xs ) ( y : ys ) = x + y : sumVec xs yssumVec what e v e r = [ ]

Whoa, dude. . . Why does this work?

I The names “what” and “ever” are patterns.

I However, a plain name (with no constructors in sight) doesnot inspect the structure of its argument.

I So “what” and “ever” will each happily match either anempty or a non-empty list.

Page 29: Real World Haskell: Lecture 2

One possible response

Let’s declare that the sum of two vectors should end when wereach the end of the shorter vector.

sumVec ( x : xs ) ( y : ys ) = x + y : sumVec xs yssumVec what e v e r = [ ]

Whoa, dude. . . Why does this work?

I The names “what” and “ever” are patterns.

I However, a plain name (with no constructors in sight) doesnot inspect the structure of its argument.

I So “what” and “ever” will each happily match either anempty or a non-empty list.

Page 30: Real World Haskell: Lecture 2

An aside: strings are lists

In Haskell, we write characters surrounded by single quotes, andstrings in double quotes. Strings are lists, so:

I ” abc ”

is syntactic sugar for

I [ ’ a ’ , ’ b ’ , ’ c ’ ]

and hence for

I ’ a ’ : ’ b ’ : ’ c ’ : [ ]

Functions that can manipulate lists can thus manipulate strings.Oh, and escape sequences such as ”\r\n\t” work, too.

Page 31: Real World Haskell: Lecture 2

We are not limited to one constructor per pattern

Suppose we want to squish consecutive repeats of an element in alist.

compress ” f o o o b a r r r r r r ”==> ” f o b a r ”

We can write a function to do this using an elegant combination ofpattern matching and guards:

compress ( x : y : y s )| x == y = compress ( y : ys )| otherwise = x : compress ( y : ys )

compress ys = ys

Notice that our pattern matches on two consecutive listconstructors!

Page 32: Real World Haskell: Lecture 2

Homework

I Write a function that returns the nth element of a list,counting from zero.

nth 2 ” squeak ”==> ’ u ’

I Write a function that returns the element immdiately beforethe last element of a list.

l a s tButOne [ 1 , 2 , 3 , 4 , 5 ]==> 4

I Write a function that determines whether its input is apalindrome.

i s P a l i n d r o m e ” f o o b a r ”==> False

i s P a l i n d r o m e ” f o o b a r r a b o o f ”==> True