Ti1220 Lecture 7: Polymorphism

Post on 27-Jan-2015

116 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

Transcript

TI1220 2012-2013Concepts of Programming Languages

Eelco Visser / TU Delft

Lecture 6: Polymorphism

Robin Milner is generally regarded as having made three major contributions to computer science. He developed LCF, one of the first tools for automated theorem proving. The language he developed for LCF, ML, was the first language with polymorphic type inference and type-safe exception handling. In a very different area, Milner also developed a theoretical framework for analyzing concurrent systems, the calculus of communicating systems (CCS), and its successor, the pi-calculus. At the time of his death, he was working on bigraphs, a formalism for ubiquitous computing subsuming CCS and the pi-calculus.[9]

http://en.wikipedia.org/wiki/Robin_Milner

Outline

Control abstraction

Polymorphism

Lab & Exam

Control Abstraction with Higher-Order Functions

Higher-order functions

• reducing code duplication, simplifying client code

Currying

• partial function applications

Writing new control structures

• growing the language

By-name parameters

• lazy evaluation

object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesEnding(query: String) = for(file <- filesHere; if file.getName.endsWith(query)) yield file}

Search for files that end in ...

def filesEnding(query: String) = for(file <- filesHere; if file.getName.endsWith(query)) yield file

def filesContaining(query: String) = for(file <- filesHere;if file.getName.contains(query)) yield file

def filesRegex(query: String) = for(file <- filesHere;if file.getName.matches(query)) yield file

Search for files that match a query ...

def filesMatching(query: String, method) = for(file <- filesHere;if file.getName.method(query)) yield file

Find the common pattern

def filesMatching(query: String, matcher: (String, String) => Boolean) = { for (file <- filesHere; if matcher(file.getName, query)) yield file}

def filesEnding(query: String) = filesMatching(query, (x:String,y:String) => x.endsWith(y))def filesContaining(query: String) = filesMatching(query, _.contains(_))def filesRegex(query: String) = filesMatching(query, _.matches(_))

Using a higher-order function

def filesMatching(query: String, matcher: (String, String) => Boolean) = { for (file <- filesHere; if matcher(file.getName, query)) yield file}

def filesEnding(query: String) = filesMatching(query, _.endsWith(_))def filesContaining(query: String) = filesMatching(query, _.contains(_))def filesRegex(query: String) = filesMatching(query, _.matches(_))

Using a higher-order function

object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles private def filesMatching(matcher: String => Boolean) = for (file <- filesHere; if matcher(file.getName)) yield file

def filesEnding(query: String) = filesMatching(_.endsWith(query)) def filesContaining(query: String) = filesMatching(_.contains(query)) def filesRegex(query: String) = filesMatching(_.matches(query))}

Using closures

def containsNeg(nums: List[Int]): Boolean = { var exists = false for (num <- nums) if (num < 0) exists = true exists}

scala> containsNeg(List(1, 2, 3, 4))res3: Boolean = false

scala> containsNeg(List(1, 2, -3, 4))res5: Boolean = true

Using explicit iteration

def containsNeg(nums: List[Int]) = nums.exists(_ < 0)

Using higher-order iteration

def containsOdd(nums: List[Int]): Boolean = { var exists = false for (num <- nums) if (num % 2 == 1) exists = true exists}

def containsOdd(nums: List[Int]) = nums.exists(_ % 2 == 1)

Explicit vs higher-order iteration

scala> def plainOldSum(x: Int, y: Int) = x + yplainOldSum: (x: Int,y: Int)Int

scala> plainOldSum(1, 2)res8: Int = 3

scala> def curriedSum(x: Int)(y: Int) = x + ycurriedSum: (x: Int)(y: Int)Int

scala> curriedSum(1)(2)res9: Int = 3

scala> def first(x : Int) = (y : Int) => x + yfirst: (x: Int)(Int) => Int

scala> val second = first(1)second: (Int) => Int = <function1>

scala> second(2)res12: Int = 3

Currying

scala> val onePlus = curriedSum(1)_onePlus: (Int) => Int = <function1>

scala> onePlus(2)res13: Int = 3

scala> val twoPlus = curriedSum(2)_twoPlus: (Int) => Int = <function1>

scala> twoPlus(2)res14: Int = 4

Currying

scala> def twice(op: Double => Double, x: Double) = op(op(x))twice: (op: (Double) => Double, x: Double)Double

scala> twice(_ + 1, 5)res15: Double = 7.0

scala> twice((x: Double) => x + 1, 5)res15: Double = 7.0

Making new control structures

def withPrintWriter(file: File, op: PrintWriter => Unit) { val writer = new PrintWriter(file) try { op(writer) } finally { writer.close() }}

withPrintWriter( new File("date.txt"), writer => writer.println(new java.util.Date))

Loan Pattern: encapsulate allocation and de-allocation of resources

scala> println("Hello, world!")Hello, world!

scala> println { "Hello, world!" }Hello, world!

Curly braces as function call

def withPrintWriter(file: File)(op: PrintWriter => Unit) { val writer = new PrintWriter(file) try { op(writer) } finally { writer.close() }}

val file = new File("date.txt")withPrintWriter(file) { writer => writer.println(new java.util.Date)}

Loan pattern using curried higher-order functions

var assertionsEnabled = truedef myAssert(predicate: () => Boolean) = if (assertionsEnabled && !predicate()) throw new AssertionError

myAssert(() => 5 > 3)

myAssert(5 > 3) // Won’t work, because missing () =>

How to defer evaluation?

def boolAssert(predicate: Boolean) = if (assertionsEnabled && !predicate) throw new AssertionError

scala> boolAssert(5 > 3)

scala> assertionsEnabled = false assertionsEnabled: Boolean = false

scala> boolAssert(10 / 0 == 0)java.lang.ArithmeticException: / by zero at .<init>(<console>:8)

Function arguments are evaluated eagerly

def byNameAssert(predicate: => Boolean) = if (assertionsEnabled && !predicate) throw new AssertionError scala> byNameAssert(5 > 3)

scala> byNameAssert(10 / 0 == 0)

By-name parameters are evaluated on demand

Re-occurring patterns

• transform every element of a list

• verify property of all elements of a list

• extract elements satisfying some criterion

• combining elements of a list using some operator

Higher-order functions

• direct, reusable definitions of such patterns

Higher-order functions

• reducing code duplication, simplifying client code

Currying

• partial function applications

Writing new control structures

• growing the language

By-name parameters

• lazy evaluation

Polymorphic Type Systems

Bool x, y;x && yx || y

!x

type

data type: a collection of data values and a set of predefined operations on those values

operations

Lecture 4

type checking: the activity of ensuring that the operands of an operation are of compatible types.

A compatible type is one that either is legal for the operator or is allowed under language rules to be implicitly converted (coerced) to a legal type

type error: the application of an operator to an operand of an inappropriate type.

Type Compatible = Type Equallimits code reuse

Lecture 4

Repeat pattern for each type

Monomorphic Functions

def map(xs: IntList, f: Int => Int): IntList = xs match { case Nil() => Nil() case Cons(y, ys) => Cons(f(y), map(ys, f))} def inc(xs: IntList) = map(xs, ((x:Int) => x + 1)) def square(xs: IntList) = map(xs, ((x:Int) => x * x))

In computer science, polymorphism is a programming language feature that allows values of different data types to be handled using a uniform interface.

The concept of parametric polymorphism applies to both data types and functions.

A function that can evaluate to or be applied to values of different types is known as a polymorphic function.

A data type that can appear to be of a generalized type (e.g., a list with elements of arbitrary type) is designated polymorphic data type like the generalized type from which such specializations are made.

Polymorphism

Inclusion polymorphism

Parametric polymorphism

Overloading

Inclusion Polymorphism

In object-oriented programming, subtype polymorphism or inclusion

polymorphism is a concept in type theory wherein a name may denote instances of many different classes as long as they are related by

some common super class. Inclusion polymorphism is generally supported through subtyping, i.e., objects of different types are

entirely substitutable for objects of another type (their base type(s)) and thus can be handled via

a common interface.

http://en.wikipedia.org/wiki/Polymorphism_(computer_science)

Type T is a set of values with some operations.

A subtype of T is a subset of the values of type T, and therefore may be used in a context

where a value of type T is expected.

char,int subtype-of long

float subtype-of double

C:

If S subtype-of T, then

Every value of S is also a value of T

A value in S can be used where a value of type T is expected

Operations of T can be applied to values in S (S inherits the operations)

Substitutability is a principle in object-oriented programming. It states that, in a computer program, if

S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S

may be substituted for objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.).

Liskov Substitution Principle

http://en.wikipedia.org/wiki/Liskov_substitution_principle

Context:- e has type T1- var x: T2 = e- def f(x: T2) and f(e)

Is T1 compatible with T2?T1 is a subtype of T2: safe, by substitutabilityT1 is not a subtype of T2: requires run-time type check

class Point { protected double x, y; public void draw() { /* ... */ }}

class Circle extends Point { private double r; public void draw() { /* ... */ }}

class Rectangle extends Point { private double w, h; public void draw() { /* ... */ }}

Point p;p = new Point(3.0, 4.0);p = new Circle(3.0, 4.0, 5.0);

TypePoint = Point(Double x Double) + Circle(Doube x Double x Double) + Rectangle(Double x Double x Double x Double)

Subtype and Inheritance in Java

class Base { Integer x; public Base(Integer v) { x = v; } public void print() { System.out.println("Base: " + x); }}class Child extends Base { Integer y; public Child(Integer v1, Integer v2) { super(v1); y = v2; } public void print() { System.out.println("Child: (" + x + "," + y + ")"); }}

class BaseTest { public static void main(String[] args) { Base base1 = new Base(45); Base base2 = new Child(567, 245); base1.print(); base2.print(); }}

Dynamic Dispatch in Java

Prototype Inheritancein JavaScript

objects in JavaScript are dynamically composed without class blueprints

⇒dynamic structural subtyping

var Duck = function(){ this.quack = function(){alert('Quaaaaaack!');}; this.feathers = function(){alert('The duck has white and gray feathers.');}; return this;}; var Person = function(){ this.quack = function(){alert('The person imitates a duck.');}; this.feathers = function(){alert('The person takes a feather from the ground and shows it.');}; this.name = function(){alert('John Smith');}; return this;}; var in_the_forest = function(duck){ duck.quack(); duck.feathers();}; var game = function(){ var donald = new Duck(); var john = new Person(); in_the_forest(donald); in_the_forest(john);}; game();

Duck typing in Java Script

http://en.wikipedia.org/wiki/Duck_typing#In_JavaScript

How to organize code reuse?

objects in JavaScript inherit from a prototype object

var o1 = Object.create({x:1, y:2}); // o1 inherits properties x and y.

var o2 = Object.create(null); // o2 inherits no props or methods.

var o3 = Object.create(Object.prototype); // o3 is like {} or new Object().

Object.create()

var p = {x:1}; // Define a prototype object.

var o = Object.create(p); // Create an object with that prototype.

p.isPrototypeOf(o) // => true: o inherits from p

Object.prototype.isPrototypeOf(p) // => true: p inherits from Object.prototype

The prototype Attribute

var o = {} // o inherits object methods from Object.prototypeo.x = 1; // and has an own property x.var p = Object.create(o); // p inherits properties from o and Object.prototypep.y = 2; // and has an own property y.var q = Object.create(p); // q inherits properties from p, o, and Object.prototypeq.z = 3; // and has an own property z.var s = q.toString(); // toString is inherited from Object.prototypeq.x + q.y // => 3: x and y are inherited from o and p

Inheritance

var unitcircle = { r:1 }; // An object to inherit from

var c = Object.create(unitcircle); // c inherits the property r

c.x = 1; c.y = 1; // c defines two properties of its own

c.r = 2; // c overrides its inherited property

unitcircle.r; // => 1: the prototype object is not affected

Inheritance

function range(from, to) { var r = Object.create(range.methods); r.from = from; r.to = to; return r;}range.methods = { includes : function(x) { return this.from <= x && x <= this.to; }, foreach : function(f) { for ( var x = Math.ceil(this.from); x <= this.to; x++) f(x); }, toString : function() { return "(" + this.from + "..." + this.to + ")"; }};var r = range(1, 3); // Create a range objectr.includes(2); // => true: 2 is in the ranger.foreach(console.log); // Prints 1 2 3console.log(r); // Prints (1...3)

Classes and Prototypes

function Range(from, to) { this.from = from; this.to = to;}Range.prototype = { includes : function(x) { return this.from <= x && x <= this.to; }, foreach : function(f) { for ( var x = Math.ceil(this.from); x <= this.to; x++) f(x); }, toString : function() { return "(" + this.from + "..." + this.to + ")"; }};var r = new Range(1, 3); // Create a range objectr.includes(2); // => true: 2 is in the ranger.foreach(console.log); // Prints 1 2 3console.log(r); // Prints (1...3)

Classes and Constructors

Constructors and Class Identity

r instanceof Range // returns true if r inherits // from Range.prototype

var F = function() {}; // This is a function object.var p = F.prototype; // This is the prototype object associated with it.var c = p.constructor; // This is the function associated with the prototype.c === F // => true: F.prototype.constructor==F for any functionvar o = new F(); // Create an object o of class Fo.constructor === F // => true: the constructor property specifies the class

Constructor Property

function Range(from, to) { this.from = from; this.to = to;} Range.prototype.includes = function(x) { return this.from <= x && x <= this.to;};Range.prototype.foreach = function(f) { for ( var x = Math.ceil(this.from); x <= this.to; x++) f(x);};Range.prototype.toString = function() { return "(" + this.from + "..." + this.to + ")";};

Extend Prototype

Parametric Polymorphism

If all code is written without mention of any specific type and thus can be used

transparently with any number of new types, it is called parametric polymorphism.

http://en.wikipedia.org/wiki/Polymorphism_(computer_science)

A mono-morphic (single-shaped) procedure can operate only on arguments of a fixed type.

A poly-morphic (many-shaped) procedure can operate uniformly on arguments of a whole family of types.

Parametric polymorphism is a type system in which we can write polymorphic procedures.

def map[A,B](xs: List[A], f: A => B): List[B] = xs match { case List() => List() case y :: ys => f(y) :: map(ys, f)} val l = map(List(1, 2, 3), ((x: Int) => x + 1))

Polymorphic function: parameterized with types

A => B : type of functions from type A to type B

Overloading

If the function denotes different and potentially heterogeneous implementations depending on a limited

range of individually specified types and combination, it is called ad-hoc polymorphism. Ad-hoc polymorphism

is supported in many languages using function and method overloading.

http://en.wikipedia.org/wiki/Polymorphism_(computer_science)

Method Overloading

scala> val c = new Rational(3,7)c: Rational = 3/7

scala> c * 2res1: Rational = 6/7

def *(that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom)

def *(i: Int): Rational = new Rational(numer * i, denom)

In a method call, the compiler picks the version of an overloaded method that correctly matches the

types of the arguments.

Lab & Exam

Graded Assignment 1Algebraic datatypes in CDynamic dispatch in C

Important datesDeadline: April 2, 2013 23:59Extension: April 5, 2013 23:59

Submitting after extension date is not possibleMaximum penalty for submitting after deadline: 6 pointsMinimum grade needed: 4Grade: 70% unit tests, 30% check listsGrade for GAs: average of four assignments

Material for exam

Slides from lectures

Tutorial exercises

Sebesta: Chapters 1-3, 5-8

Programming in Scala: Chapters 1, 4-9, 15-16

K&R C: Chapters 1-6

JavaScript Good Parts: Chapters 1-4

Content of exam

20% multiple choice questions about concepts

40% Scala programming (functional programming)

20% C programming (structures and pointers)

20% JavaScript programming (objects and prototypes)

http://department.st.ewi.tudelft.nl/weblab/assignment/850

Answers will be published when 80 students have completed the exam

Registration for Exam is Required

http://department.st.ewi.tudelft.nl/weblab/assignment/759 → Your Submission

Good Luck!

top related