KEMBAR78
PL 04TypesAndPolymorphism | PDF | Data Type | Formal Methods
100% found this document useful (1 vote)
94 views59 pages

PL 04TypesAndPolymorphism

This document discusses types and polymorphism in programming languages. It begins by distinguishing between static and dynamic types, noting that values have static types defined by the language but dynamic types determined at runtime. It then covers type completeness, describing how functional languages aim to treat all types uniformly as first-class values. The document outlines different types in Haskell, including function, list, tuple, and user-defined data types. It also introduces monomorphic and polymorphic types as well as Hindley-Milner type inference.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
94 views59 pages

PL 04TypesAndPolymorphism

This document discusses types and polymorphism in programming languages. It begins by distinguishing between static and dynamic types, noting that values have static types defined by the language but dynamic types determined at runtime. It then covers type completeness, describing how functional languages aim to treat all types uniformly as first-class values. The document outlines different types in Haskell, including function, list, tuple, and user-defined data types. It also introduces monomorphic and polymorphic types as well as Hindley-Milner type inference.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 59

4.

Types and Polymorphism

Oscar Nierstrasz

e so k
ty p
Static
ti c s o k
Se man

ta xo k
S y n
m te xt
a
Progr
Roadmap

> Static and Dynamic Types


> Type Completeness
> Types in Haskell
> Monomorphic and Polymorphic types
> Hindley-Milner Type Inference
> Overloading
References

> Paul Hudak, “Conception, Evolution, and Application of Functional


Programming Languages,” ACM Computing Surveys 21/3, Sept.
1989, pp 359-411.
> L. Cardelli and P. Wegner, “On Understanding Types, Data
Abstraction, and Polymorphism,” ACM Computing Surveys, 17/4, Dec.
1985, pp. 471-522.
> D. Watt, Programming Language Concepts and Paradigms, Prentice
Hall, 1990

3
Roadmap

> Static and Dynamic Types


> Type Completeness
> Types in Haskell
> Monomorphic and Polymorphic types
> Hindley-Milner Type Inference
> Overloading
What is a Type?

Type errors:

? 5 + [ ]
ERROR: Type error in application
*** expression : 5 + [ ]
*** term : 5
*** type : Int
*** does not match : [a]

A type is a set of values?


> int = { ... -2, -1, 0, 1, 2, 3, ... }
> bool = { True, False }
> Point = { [x=0,y=0], [x=1,y=0], [x=0,y=1] ... }

5
The notion of a type as a set of values is very natural and
intuitive: Integers are a set of values; the Java type JButton
corresponds to all possible instance of the JButton class (or any
of its possible subclasses).
What is a Type?

A type is a partial specification of behaviour?

> n,m:int ⇒ n+m is valid, but not(n) is an error

> n:int ⇒ n := 1 is valid, but n := “hello world” is an error

What kinds of specifications are interesting? Useful?

6
A Java interface is a simple example of a partial specification of
behaviour. Any object that conforms to a given interface can be
used where that interface is expected. This is arguably more
useful than the notion of a type as a set of values, because we may
not care about the specific internal representation of an object
but just what it can do for us.
Static and Dynamic Types

Values have static types defined by the programming


language. A variable may have a declared, static type.
Variables and expressions have dynamic types determined
by the values they assume at run time.
declared, static type is Applet

static type of value is GameApplet

Applet myApplet = new GameApplet();

actual dynamic type is GameApplet


7
The terms “static” and “dynamic” can be very confusing,
especially given their use as keywords in languages like C++ and
Java.

“Static” simply means: “based on program text alone.” Static


typing (for example) means types are checked based on the source
code, not by executing the program. Static analysis more
generally means “analysis based on source code alone.”

“Dynamic” means: “determined at run time.” So, dynamic


analysis means “analysis based on run-time behaviour.”

Aside: “run time” (noun) = execution time; “run-time” (adjective) =


something happening at run time; “runtime” (noun) = run-time language
support, e.g., in the virtual machine.
Static types restrict the programs you may write!

e s o k
c ty p
Stati
ti c s o k
a n
Sem
x o k
Synta xt
ra m te
Pro g

Object wyatt = new Cowboy();


wyatt.draw();
8
A static type system (typically) forbids you from running
programs that the type system cannot validate.

This Java code will not run without the explicit downcast (int),
even though the downcast does nothing.

List<Object> myList = new ArrayList<Object>();


myList.add(10);
return 2 + (int) myList.get(1);
Static and Dynamic Typing

A language is statically typed if it is always possible to determine the


(static) type of an expression based on the program text alone.

A language is dynamically typed if only values have fixed type. Variables


and parameters may take on different types at run-time, and must be
checked immediately before they are used.

A language is “strongly typed” if it is impossible to perform an operation


on the wrong kind of object.

Type consistency may be assured by


I. compile-time type-checking,
II. type inference, or
III.dynamic type-checking.
9
The term “strongly typed” is not very meaningful, just as the term
“untyped” is misleading. Actually all programming languages
have a notion of type; they just handle types in very different
ways.
The more useful distinction whether a language is statically-
typed, like Java or C++, or dynamically-typed, like Smalltalk or
Ruby.
Haskell is interesting in that it is statically-typed, but does not
require explicit type annotations.
Strong, weak, static, dynamic

Static Dynamic

“Strong” Java, Pascal Smalltalk, Ruby

“Weak” C Assembler

10
Kinds of Types

All programming languages provide some set of built-in


types.
> Primitive types: booleans, integers, floats, chars ...
> Composite types: functions, lists, tuples ...

Most statically-typed modern languages provide for


additional user-defined types.
> User-defined types: enumerations, recursive types,
generic types, objects ...

11
Roadmap

> Static and Dynamic Types


> Type Completeness
> Types in Haskell
> Monomorphic and Polymorphic types
> Hindley-Milner Type Inference
> Overloading
Type Completeness

The Type Completeness Principle:


No operation should be arbitrarily restricted in the
types of values involved.
— Watt
First-class values can be evaluated, passed as arguments
and used as components of composite values.

Functional languages attempt to make no class distinctions,


whereas imperative languages typically treat functions (at
best) as second-class values.

13
Pretty much all programming languages limit the kinds of entities
that may be pass as values (and therefore have a meaningful
type). In C or C++, functions are not values, though pointers to
functions are. Classes are not values.
In Java, methods and classes are not values, though you can
obtain a reified object representing a class as a value, and in Java
8, you can pass method references as values. Packages are not
values, however.
In Haskell, functions are first-class values, so can be passed as
arguments and returned as values. Since Haskell is statically-
typed, the type system is capable of expressing function types.
Roadmap

> Static and Dynamic Types


> Type Completeness
> Types in Haskell
> Monomorphic and Polymorphic types
> Hindley-Milner Type Inference
> Overloading
Function Types

Function types allow one to deduce the types of


expressions without the need to evaluate them:
fact :: Int -> Int
42 :: Int ⇒ fact 42 :: Int

Curried types:
Int -> Int -> Int ≡ Int -> (Int -> Int)
and
plus 5 6 ≡ ((plus 5) 6)
so:
plus::Int->Int->Int ⇒ plus 5::Int->Int

15
All expressions are typed. If you know the types of the individual
sub-expressions, you can compute the type of the expression as a
whole.
In Haskell, you can query the type of a value with the operator :t
Prelude> :t 'a'
'a' :: Char
Prelude> :t 1
1 :: Num a => a
Note that numbers have a “funny” type: “Num a => a” means “if
the type a is a kind of number, then the type is a”. Since Int and
Float are both kinds of number, we can be more specific:
Prelude> :t 1 :: Int
1 :: Int :: Int
Prelude> :t 1 :: Float
1 :: Float :: Float
List Types

A list of values of type a has the type [a]:


[ 1 ] :: [ Int ]

NB: All of the elements in a list must be of the same type!


['a', 2, False] -- illegal! can’t be typed!

16
Tuple Types

If the expressions x1, x2, ..., xn have types t1,


t2, ..., tn respectively, then the tuple (x1,
x2, ..., xn) has the type (t1, t2, ..., tn):

(1, [2], 3) :: (Int, [Int], Int)


('a', False) :: (Char, Bool)
((1,2),(3,4)) :: ((Int, Int), (Int, Int))

The unit type is written () and has a single element which


is also written as ().

17
User Data Types

New data types can be introduced by specifying


I. a datatype name,
II.a set of parameter types, and
III.a set of constructors for elements of the type:

data DatatypeName a1 ... an = constr1 | ... | constrm

where the constructors may be either:


1. Named constructors:

Name type1 ... typek

2. Binary constructors (i.e., anything starting with “:”):

type1 BINOP type2


18
Data types are somehow comparable to classes in object-oriented
languages, yet also very different. A constructor is used to create
a value of an abstract data type, but each value must be
deconstructed before it can be used.
Deconstruction is simply pattern matching using the constructor
names: if a value matches a constructor, then that gives you
access to the arguments used with that constructor to create that
value in the first place.
Enumeration types

User data types that do not hold any data can model enumerations:

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

Functions over user data types must deconstruct the arguments, with
one case for each constructor:

whatShallIDo Sun = “relax”


whatShallIDo Sat = “go shopping”
whatShallIDo _ = “guess I'll have to go to work”

19
Notice how the data type Day has seven constructors, each
without any arguments.
To find out which value a day is, we just pattern match it against
its constructors, thus revealing what it is.
This is the opposite of encapsulation in OO languages, where you
never deconstruct a value to access its data — instead the object
has its own methods (services) that have exclusive access to the
hidden data.
Union types

data Temp = Centigrade Float | Fahrenheit Float

freezing :: Temp -> Bool


freezing (Centigrade temp) = temp <= 0.0
freezing (Fahrenheit temp) = temp <= 32.0

20
In this case Temp has two constructors, each of which takes a
single number as an argument. By pattern matching, we
deconstruct the value and gain access to the hidden data inside.
Recursive Data Types

A recursive data type provides constructors over the type itself:

data Tree a = Lf a | Tree a :^: Tree a


mytree = (Lf 12 :^: (Lf 23 :^: Lf 13)) :^: Lf 10

? :t mytree
➪ mytree :: Tree Int

21
Recall that binary constructors are operators that start with “:”.
In order to suggest tree-like structures, we pick “:^:” as our
constructor for building a tree out of two subtrees.
Trees therefore have two constructors: Lf, for building a leaf
node from a single element of an arbitrary type, and :^: for
building a composite tree from two subtrees containing elements
of consistent types.
Note that Haskell allows us to build a tree of strings or a tree of
integers, but not a tree of integers and strings.

Can you define a tree data type in Haskell that could hold either
integers or strings? If so, how? If not, why not?
Using recursive data types

leaves, leaves' :: Tree a -> [a]


leaves (Lf l) = [l]
leaves (l :^: r) = leaves l ++ leaves r

leaves' t = leavesAcc t [ ]
where leavesAcc (Lf l) = (l:)
leavesAcc (l :^: r) = leavesAcc l . leavesAcc r

NB: (f . g) x = f (g x)
✎ What do these functions do?
✎ Which function should be more efficient? Why?
✎ What is (l:) and what does it do?

22
Recall that : is Haskell's built-in operator for constructing lists
from a head and a tail.
Prelude> :t (:)
(:) :: a -> [a] -> [a]
Since : is a Curried function, we can supply one argument and
get a function back. What does this function do?

NB: Dot (.) is the traditional function composition operator from


mathematics. It is also a Curried function:
Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
Roadmap

> Static and Dynamic Types


> Type Completeness
> Types in Haskell
> Monomorphic and Polymorphic types
> Hindley-Milner Type Inference
> Overloading
Monomorphism

Languages like Pascal and C have monomorphic type


systems: every constant, variable, parameter and
function result has a unique type.
> good for type-checking
> bad for writing generic code
—it is impossible in Pascal to write a generic sort procedure

24
“Monomorphic” simple means “every value has a single, unique
type”.
“Polymorphic” means “values may have more than one type.”
Polymorphism

A polymorphic function accepts arguments of different types:

length :: [a] -> Int


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

map :: (a -> b) -> [a] -> [b]


map f [ ] = [ ]
map f (x:xs) = f x : map f xs

(.) :: (b -> c) -> (a -> b) -> (a -> c)


(f . g) x = f (g x)

25
Kinds of Polymorphism

> Universal polymorphism:


—Parametric: polymorphic map function in Haskell; nil/void pointer
type in Pascal/C
—Inclusion: subtyping — graphic objects

> Ad Hoc polymorphism:


—Overloading: + applies to both integers and reals
—Coercion: integer values can be used where reals are expected
and v.v.

26
The kind of polymorphism supported by Haskell is also known as
parametric polymorphism (functions may be generic in their
parameter types). Object-oriented languages also support
inclusion or subtype polymorphism, another kind of
polymorphism that is not part of Haskell.

More on this in the lecture on “Objects and Types”.


Roadmap

> Static and Dynamic Types


> Type Completeness
> Types in Haskell
> Monomorphic and Polymorphic types
> Hindley-Milner Type Inference
> Overloading
Type Inference

We can infer the type of many expressions by simply examining their


structure. Consider:

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

Clearly:
length :: a -> b
Furthermore, b is obvious int, and a is a list, so:
length :: [c] -> Int

We cannot further refine the type, so we are done.

28
Note how the definition of length deconstructs lists by pattern-
matching using the list constructors, i.e., [] to construct an
empty list and : to construct a non-empty list.

By examining the definition of length, we can infer its type.


First, it is clearly a function with a single argument and return
value, so its shape is clearly a -> b (where a and b are types or
type variables).
Second, a is clearly a list, so its type can be refined to [c], and b
is clearly a number (let's say Int). Conclusion:
length :: [c] -> Int
Composing polymorphic types

We can deduce the types of expressions using polymorphic


functions by simply binding type variables to concrete types.

Consider:
length :: [a] -> Int
map :: (a -> b) -> [a] -> [b]

Then:
map length :: [[a]] -> [Int]
[ “Hello”, “World” ] :: [[Char]]
map length [ “Hello”, “World” ] :: [Int]

29
Note that type variables like a or b can be bound to any Haskell
type, no matter how complex.
Polymorphic Type Inference

Hindley-Milner Type Inference automatically determines


the types of many polymorphic functions.

map f [] = []
map f (x:xs) = f x : map f xs

map :: X -> Y -> Z

map :: (a -> b) -> [c] -> [d]


xxxx

map :: (a -> b) -> [a] -> [b]

The corresponding type system is used in many modern


functional languages, including ML and Haskell.
30
The Hindley-Milner type inference algorithm was discovered
independently first by J. Roger Hindley and later by Robin
Milner.

Here we only sketch out the main idea, not the details of the
algorithm itself. The algorithm is linear in the size of the source
code, so it is very practical for real language implementations.
Note that the algorithm only deals with parametric
polymorphism, not subtype polymorphism, so it cannot be used
for languages like Java.
Type Specialization

A polymorphic function may be explicitly assigned a more specific type:

idInt :: Int -> Int


idInt x = x

Note that the :t command can be used to find the type of a particular
expression that is inferred by Haskell:

? :t \x -> [x]
➪ \x -> [x] :: a -> [a]

? :t (\x -> [x]) :: Char -> String


➪ \x -> [x] :: Char -> String

31
Roadmap

> Static and Dynamic Types


> Type Completeness
> Types in Haskell
> Monomorphic and Polymorphic types
> Hindley-Milner Type Inference
> Overloading
Coercion vs overloading

Coercion or overloading — how do you distinguish?

3 + 4
3.0 + 4
3 + 4.0
3.0 + 4.0

✎ Are there several overloaded + functions, or just one, with


values automatically coerced?

33
Are there four different overloaded + functions; two, with
coercion to real if one arg is int; or one with coercion to real?
Overloading

Overloaded operators are introduced by means of type classes:

class Eq a where
(==), (/=) :: a -> a -> Bool
x /= y = not (x == y)
-- NB: defined in standard prelude

A type class must be instantiated to be used:

instance Eq Bool where


True == True = True
False == False = True
_ == _ = False

34
Note that type classes have nothing to do with classes in OO
languages! A type class in Haskell has more affinity with
interfaces in Java: A type class defines a set of overloaded
operators that must all be implemented by a given data type.
Instantiating overloaded operators

For each overloaded instance a separate definition must be given

instance Eq Int where (==) = primEqInt

instance Eq Char where c == d = ord c == ord d

instance (Eq a, Eq b) => Eq (a,b) where


(x,y) == (u,v) = x==u && y==v

instance Eq a => Eq [a] where


[ ] == [ ] = True
[ ] == (y:ys) = False
(x:xs) == [ ] = False
(x:xs) == (y:ys) = x==y && xs==ys

35
Equality for Data Types

Why not automatically provide equality for all types of values?

User data types:

data Set a = Set [a]


instance Eq a => Eq (Set a) where
Set xs == Set ys = xs `subset` ys && ys `subset` xs
where xs `subset` ys = all (`elem` ys) xs

✎ How would you define equality for the Tree data type?

NB: all (‘elem’ ys) xs tests that every x in xs is an element of ys


36
Equality for Functions

Functions:

? (1==) == (\x->1==x)
ERROR: Cannot derive instance in expression
*** Expression : (==) d148 ((==) {dict} 1) (\x->(==) {dict} 1 x)
*** Required instance : Eq (Int -> Bool)

Determining equality of functions is undecidable in general!

37
What you should know!

✎ How are the types of functions, lists and tuples specified?


✎ How can the type of an expression be inferred without
evaluating it?
✎ What is a polymorphic function?
✎ How can the type of a polymorphic function be inferred?
✎ How does overloading differ from parametric
polymorphism?
✎ How would you define == for tuples of length 3?
✎ How can you define your own data types?
✎ Why isn’t == pre-defined for all types?
Can you answer these questions?

✎ Can any set of values be considered a type?


✎ Why does Haskell sometimes fail to infer the type of an
expression?
✎ What is the type of the predefined function all? How
would you implement it?
✎ How would you define equality for the Tree data type?
Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)

You are free to:


Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material for any purpose, even commercially.

The licensor cannot revoke these freedoms as long as you follow the license terms.

Under the following terms:

Attribution — You must give appropriate credit, provide a link to the license, and indicate if
changes were made. You may do so in any reasonable manner, but not in any way that
suggests the licensor endorses you or your use.

ShareAlike — If you remix, transform, or build upon the material, you must distribute your
contributions under the same license as the original.

No additional restrictions — You may not apply legal terms or technological measures that legally
restrict others from doing anything the license permits.

http://creativecommons.org/licenses/by-sa/4.0/

You might also like