real world haskell: lecture 3

38
Real World Haskell: Lecture 3 Bryan O’Sullivan 2009-10-21

Upload: bryan-osullivan

Post on 29-Aug-2014

2.255 views

Category:

Education


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Real World Haskell: Lecture 3

Real World Haskell:Lecture 3

Bryan O’Sullivan

2009-10-21

Page 2: Real World Haskell: Lecture 3

Types: what’s the big deal?

If you’ve been following along and trying out homework, you’reprobably intimately familiar with the facts that

I Haskell is a statically typed language (it checks types beforewe run), and

I and it’s quite strict about this (it’s strongly typed).

Page 3: Real World Haskell: Lecture 3

What does “strongly typed” actually mean?

DefinitionA strong type system guarantees that a program cannot containcertain kinds of errors.

Strength and dynamism are more or less independent concepts in atype system.

There are degrees of both strength and dynamism in type systems,and often disagreement on both, so the likelihood of confusionwhen talking to someone about types is high.

Page 4: Real World Haskell: Lecture 3

Familiar types

Haskell provides a number of types similar to those you’ll have seenin other languages:

I Int is a fixed-width signed integer type, usually the system’snative word size (32 or 64 bits).

I Char is a Unicode character.

I Double is the double-precision floating point integer type,usually 64 bits.

I Integer is a signed integer type of unbounded size(unbounded if you have infinite memory, that is).

I Bool is the Boolean type, with possible values True andFalse.

Page 5: Real World Haskell: Lecture 3

Expressions and types

Every expression (and hence every value) in Haskell has a type.

The expression ’a’ has the type Char. We can write this asfollows:

a : : Chara = ’ a ’

You can read the syntax “ :: ” as “has the type,” so:

I The notation “a :: Char” is called a type signature, and

I has the meaning “the expression named a has the type Char.”

Page 6: Real World Haskell: Lecture 3

Type inference

Have you noticed that most of the homework answers submitted sofar contain no type signatures?

This is because the Haskell compiler infers what the type of everyexpression and definition is.

When you write a = ’a’, the compiler knows that the expression’a’ can only have the type Char, so the variable a must have thistype too.

Page 7: Real World Haskell: Lecture 3

Type inference and concision

Unlike other statically typed languages you may be familiar with,hand-written type annotations are optional1 in Haskell.

This is one of the big factors that lets us write amazingly concisecode:

I We don’t need to tell the compiler what our types are when itcan figure this out for itself.

1Well, they’re almost always optional.

Page 8: Real World Haskell: Lecture 3

Functions have types, too

f a c t : : Integer −> Integer

f a c t n | n < 0 = e r r o r ” n e g a t i v e ! ”| otherwise = go n

where go 0 = 1go i = i ∗ go ( i −1)

A “−>” symbol in a signature denotes the type of a function.

I On the left of the −> is the type of the argument.

I The type on the right is that of the result.

Read the arrow and its neighbours as “this is a function from thetype Integer to the type Integer.”

Page 9: Real World Haskell: Lecture 3

Functions with multiple arguments

Suppose we have a function with more than one argument. Here’show we write its type signature.

cons : : Char −> Str ing −> Str ing

cons x xs = x : xs

Every item in the chain of arrows, except the last, is the type ofthe argument at that position. The last item in the chain is thetype of the result.

I What is the type of x above?

I What is the type of xs?

I What is the function’s result type?

Page 10: Real World Haskell: Lecture 3

Tuples

An easy way to create a collection of values is using a tuple.

b l a r g l e : : ( Char , Int , Bool )b l a r g l e = ( ’ a ’ , 42 , False )

The type signature (optional, as usual!) indicates what the type ofeach element in the tuple is.

I A tuple of two elements is usually called a pair.

I Tuples with three elements are often called triples.

I Larger tuples are 4-tuples, 5-tuples, and in general, n-tuples.(But 4-tuples and larger are very rare in the wild.)

There is no one-tuple type (it would be redundant).

Page 11: Real World Haskell: Lecture 3

More about tuples

It should be obvious that the following two tuple types can expressdifferent numbers of values:

I (Bool, Bool)

I (Bool, Bool, Char)

As far as the type checker is concerned, they are indeed completelydistinct types.

This suggests that the following expressions might have differenttypes:

I (True, ’a’)

I (’a’, True)

What do you think?

Page 12: Real World Haskell: Lecture 3

Functions on tuples

Pairs are so common in Haskell that there are built-in functions,fst and snd for accessing the first and second elements of a pair:

f s t ( 1 , ’ a ’ )==> 1

snd ( 1 , ’ a ’ )==> ’ a ’

Page 13: Real World Haskell: Lecture 3

Pattern matching on tuples

For larger tuples, built-in accessor functions are not supplied, sincethose types are used much less often.

You can define your own functions using pattern matching, and thesyntax is as you’d expect:

f s t 3 ( a , b , c ) = asnd3 ( a , b , c ) = bthd3 ( a , b , c ) = c

In practice, it’s more common to pattern-match than to define anduse accessor functions.

Page 14: Real World Haskell: Lecture 3

The list type

The ghci interpreter has a very useful command, :type, forfiguring out what the type of an expression or function is.

Here’s an example:

Prelude> :type lineslines :: String -> [String]

As this suggests, the notation for “list of String” is [String].

And as the notation suggests, every element in a list must have thesame type.

Page 15: Real World Haskell: Lecture 3

Type synonyms

Recall that a Haskell string is just a list of characters.

The type of a list of characters is [Char], but we often see Stringin type signatures.

There is no magic at work here: String is just a synonym for[Char]. The compiler treats them identically.

In fact, we can introduce our own type synonyms at will:

type StreetNumber = Inttype StreetName = Str ingtype Address = ( StreetNumber , StreetName )

(Think typedef, C programmers!)

Page 16: Real World Haskell: Lecture 3

Functions over lists

Type synonyms are only mildly interesting; I mentioned themmainly to bring the String ≡ [Char] equivalence to the fore.

Here’s why: notice that most functions on lists don’t seem to carewhat types the elements of those lists have:

drop 3 [ 0 . . 1 0 ]==> [ 3 , 4 , 5 , 6 , 7 ]

drop 5 ” a b a c i n a t e ”==> ” n at e ”

What’s going on here?

Page 17: Real World Haskell: Lecture 3

Polymorphism

Consider the following function definition:

take n | n <= 0 = [ ]take [ ] = [ ]take n ( x : xs ) = x : take ( n−1) xs

The take function never inspects the elements of the list. Itneither knows nor cares what they are.

We call functions like this polymorphic over the list’s elements.

Page 18: Real World Haskell: Lecture 3

Polymorphic type signatures

How do we write the type signature for take?

take : : Int −> [ a ] −> [ a ]

That name “a” above, inside the list brackets, is a type variable.

A type variable lets us say “I don’t know or care what concretetype2 will be used in this position, so here’s a placeholder.”

2“Concrete type”? Think Int, Char, etc.

Page 19: Real World Haskell: Lecture 3

Multiple type variables

We are not limited to a single type variable in a type signature.

t r i p l e : : a −> b −> c −> ( a , b , c )t r i p l e x y z = ( x , y , z )

This means:

I The first argument could have any type a.

I The second argument could have any type b. This type couldbe the same as, or different to, a.

I And the third? You get the idea.

Page 20: Real World Haskell: Lecture 3

Functions as parameters

Suppose we want to inspect every element of a list, and drop thosethat do not suit some criterion.

How should we write the type signature of a function that checksan element of the list?

We’re checking some element of type a, and to indicate whether itpasses or fails, we should return a Bool. So our type is:

a −> Bool

How would we pass such a predicate3 as an argument to anotherfunction that will perform the filtering?

3Fancy language for “function returning a Bool.”

Page 21: Real World Haskell: Lecture 3

Functions as parameters

Suppose we want to inspect every element of a list, and drop thosethat do not suit some criterion.

How should we write the type signature of a function that checksan element of the list?

We’re checking some element of type a, and to indicate whether itpasses or fails, we should return a Bool. So our type is:

a −> Bool

How would we pass such a predicate3 as an argument to anotherfunction that will perform the filtering?

3Fancy language for “function returning a Bool.”

Page 22: Real World Haskell: Lecture 3

Functions as parameters

Suppose we want to inspect every element of a list, and drop thosethat do not suit some criterion.

How should we write the type signature of a function that checksan element of the list?

We’re checking some element of type a, and to indicate whether itpasses or fails, we should return a Bool. So our type is:

a −> Bool

How would we pass such a predicate3 as an argument to anotherfunction that will perform the filtering?

3Fancy language for “function returning a Bool.”

Page 23: Real World Haskell: Lecture 3

The definition of filter

Welcome to the world of higher-order programming!

f i l t e r : : ( a −> Bool ) −> [ a ] −> [ a ]

f i l t e r [ ] = [ ]f i l t e r p ( x : xs )

| p x = x : f i l t e r p xs| otherwise = f i l t e r p xs

Higher-order programming occurs when we pass around functionsas arguments to other functions. Simple, but amazingly powerful!

Page 24: Real World Haskell: Lecture 3

Turning a polymorphic type into another type

Suppose we want to use the take function on a list of Int values.

How do we figure out what the type of the function will be whenwe use it in that context?

Take the type variable a, and substitute our desired type Inteverywhere. So

take : : Int −> [ a ] −> [ a ]

when applied to a list of Int becomes

take : : Int −> [ Int ] −> [ Int ]

Page 25: Real World Haskell: Lecture 3

Polymorphism and . . . polymorphism

We can do the same rewrite-the-types trick when we want to seewhat the type of a polymorphic function would be when used withanother polymorphic type.

What do I mean by this? Consider take for lists-of-lists-of-lists.

Let’s write a polymorphic list as [b], so a polymorphic list of listsis [[ b ]] .

We’ll thus replace a with [[ b ]] to get this type:

take : : Int −> [ [ [ b ] ] ] −> [ [ [ b ] ] ]

Page 26: Real World Haskell: Lecture 3

Polymorphism vs type inference (I)

Here’s a definition someone wrote for homework, but they gotconfused along the way, since we hadn’t covered types yet:

nth n [ ] = [ ]nth n ( x : xs )| n < 0 = [ ]| n == 0 = x| otherwise = nth ( n−1) xs

Try reasoning about this code.

I What was the intended type for the nth function?

I What is the type of this version, and why?

Page 27: Real World Haskell: Lecture 3

Polymorphism vs type inference (II)

The original type for nth should have been this:

nth : : Int −> [ a ] −> a

But because we didn’t supply a type, and the code wasn’t quitedoing the right thing, the compiler inferred a surprising type:

nth : : Int −> [ [ a ] ] −> [ a ]

Notice that its inference was both correct and consistent with whatthe code was really doing (but not with what the author initiallythought the code was doing).

Page 28: Real World Haskell: Lecture 3

How does this work in practice?

In real-world code, when you omit type signatures, you place noconstraints on the compiler’s type inference engine.

I Therefore, a single type error can lead the inference engine offin wild logical directions.

I If your code passes the typechecker’s scrutiny, you’re not outof the woods: it may not behave as you expect, since its typesmay be different than you thought.

I If your code fails to typecheck, the errors are likely to beconfusing, since you gave the compiler no hints about whatyou really meant.

Page 29: Real World Haskell: Lecture 3

Polymorphism and type annotations

What does this experience suggest?

I Explicit type signatures can actually be useful!

I A signature indicates what type we think the code has.

I Type inference occurs even when we supply an explicit type.

I If our code’s type signature is inconsistent with the type thecompiler infers, we will get a useful compilation error.

Page 30: Real World Haskell: Lecture 3

Real world use of type annotations

Practical coding tips:

I Add type signatures to almost all of your top-level definitions.

I They’re good for more than the compiler—they’re invaluableto readers, as documentation of your code’s behaviour!

I Let the compiler infer types for almost all local definitions,i.e. those in let and where clauses.

Page 31: Real World Haskell: Lecture 3

Inferring behaviour from types

In many cases, we can guess what a function probably does, simplyby examining its type (and sometimes its name):

z ip : : [ a ] −> [ b ] −> [ ( a , b ) ]

r e p l i c a t e : : Int −> a −> [ a ]

s p l i t A t : : Int −> [ a ] −> ( [ a ] , [ a ] )

Page 32: Real World Haskell: Lecture 3

Inferring behaviour from types

In many cases, we can guess what a function probably does, simplyby examining its type (and sometimes its name):

z ip : : [ a ] −> [ b ] −> [ ( a , b ) ]

r e p l i c a t e : : Int −> a −> [ a ]

s p l i t A t : : Int −> [ a ] −> ( [ a ] , [ a ] )

Page 33: Real World Haskell: Lecture 3

Inferring behaviour from types

In many cases, we can guess what a function probably does, simplyby examining its type (and sometimes its name):

z ip : : [ a ] −> [ b ] −> [ ( a , b ) ]

r e p l i c a t e : : Int −> a −> [ a ]

s p l i t A t : : Int −> [ a ] −> ( [ a ] , [ a ] )

Page 34: Real World Haskell: Lecture 3

What polymorphism tells us

When we see a polymorphic function like this:

myLength : : [ a ] −> Int

We can make a surprisingly strong statement about its behaviour:

I Its behaviour cannot be influenced by either the type or thevalues of the elements in the list it receives.

Why is this?

I Polymorphism is a way of saying “I cannot know anythingabout this type, or inspect or manipulate its values.”

For example, there’s no way to say “myLength should return adifferent result for [Char] than for [ Int ].” That’s pretty profound!

Page 35: Real World Haskell: Lecture 3

What polymorphism tells us

When we see a polymorphic function like this:

myLength : : [ a ] −> Int

We can make a surprisingly strong statement about its behaviour:

I Its behaviour cannot be influenced by either the type or thevalues of the elements in the list it receives.

Why is this?

I Polymorphism is a way of saying “I cannot know anythingabout this type, or inspect or manipulate its values.”

For example, there’s no way to say “myLength should return adifferent result for [Char] than for [ Int ].” That’s pretty profound!

Page 36: Real World Haskell: Lecture 3

What polymorphism tells us

When we see a polymorphic function like this:

myLength : : [ a ] −> Int

We can make a surprisingly strong statement about its behaviour:

I Its behaviour cannot be influenced by either the type or thevalues of the elements in the list it receives.

Why is this?

I Polymorphism is a way of saying “I cannot know anythingabout this type, or inspect or manipulate its values.”

For example, there’s no way to say “myLength should return adifferent result for [Char] than for [ Int ].” That’s pretty profound!

Page 37: Real World Haskell: Lecture 3

Homework (I)

Write a function that drops every nth element from a list. Itshould return the removed elements in one element of a pair, andthe remainder of the list in another. Provide a type signature foryour function.

e v e r y 3 ” a b c d e f g h i ”==> ( ” c f i ” , ” abdegh ” )

Write a function that returns the kth least element of a list,starting from zero as “the least element”.

kminimum 0 [ 3 , 2 , 1 ]==> 1

kminimum 2 ” qwerty ”==> ’ r ’

Page 38: Real World Haskell: Lecture 3

Homework (II)

From inspection of the type and operation of the built-in zipWithfunction, write your own implementation.

myZipWith min [ 1 , 2 , 3 ] [ 2 , 2 , 2 , 2 , 2 ]==> [ 1 , 2 , 2 ]

Write a function that capitalizes the first letter of every word in astring. It must preserve whitespace and punctuation.

u c f i r s t ” f o o bar ! ! bAZ”==> ”Foo Bar ! ! BAZ”