Real World Haskell: Lecture 2

Post on 12-May-2015

1.527 views 0 download

Tags:

Transcript of Real World Haskell: Lecture 2

Real World Haskell:Lecture 2

Bryan O’Sullivan

2009-10-14

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

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.

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.

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 : []

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 : []

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’ : []

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 ! ”

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?

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.

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 )

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 ?

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.

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.

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.

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.

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?

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?

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.

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.

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.

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 [ ] [ ] = [ ]

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

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.

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.

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?

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?

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.

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.

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.

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!

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