Ti1220 Lecture 7: Polymorphism
-
Upload
eelco-visser -
Category
Documents
-
view
117 -
download
0
description
Transcript of Ti1220 Lecture 7: 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
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
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.
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
objects in JavaScript are dynamically composed without class blueprints
⇒dynamic structural subtyping
When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.
James Whitcomb Riley
In computer programming with object-oriented programming languages, duck typing is a style of
dynamic typing in which an object's methods and properties determine the valid semantics, rather than its inheritance
from a particular class or implementation of a specific interface.
http://en.wikipedia.org/wiki/Duck_typing
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
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
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
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.
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
Syntax and SemanticsNames, Bindings, and Scopes Storage Data TypesFunctional ProgrammingFirst-class FunctionsPolymorphism
Type ParameterizationParsing and InterpretationData Abstraction / Modular ProgrammingFunctional Programming ReduxConcurrencyConcurrent ProgrammingDomain-Specific Languages
Quarter 3
Quarter 4
Basics ofScalaJavaScriptC
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