Lecture 6: Polymorphism                     TI1220 2012-2013             Concepts of Programming Languages                ...
Robin Milner is generally regarded as havingmade three major contributions to computerscience. He developed LCF, one of th...
Outline  Control abstraction  Polymorphism  Lab & Exam
Control Abstraction withHigher-Order Functions
Higher-order functions• reducing code duplication, simplifying client codeCurrying• partial function applicationsWriting n...
Search for files that end in ... object FileMatcher {   private def filesHere = (new java.io.File(".")).listFiles   def fil...
Search for files that match a query ...def filesEnding(query: String) =  for(file <- filesHere; if file.getName.endsWith(qu...
Using a higher-order functiondef filesMatching(query: String,  matcher: (String, String) => Boolean) = {  for (file <- fil...
Using a higher-order functiondef filesMatching(query: String,  matcher: (String, String) => Boolean) = {  for (file <- fil...
Using closuresobject FileMatcher {  private def filesHere = (new java.io.File(".")).listFiles    private def filesMatching...
def containsNeg(nums: List[Int]): Boolean = {  var exists = false  for (num <- nums)    if (num < 0)      exists = true   ...
def containsOdd(nums: List[Int]): Boolean = {  var exists = false  for (num <- nums)    if (num % 2 == 1)      exists = tr...
scala> def plainOldSum(x: Int, y: Int) = x + yplainOldSum: (x: Int,y: Int)Intscala> plainOldSum(1, 2)                     ...
Curryingscala> val onePlus = curriedSum(1)_onePlus: (Int) => Int = <function1>scala> onePlus(2)res13: Int = 3scala> val tw...
scala> def twice(op: Double => Double, x: Double) = op(op(x))twice: (op: (Double) => Double, x: Double)Doublescala> twice(...
Loan Pattern: encapsulate allocation          and de-allocation of resourcesdef withPrintWriter(file: File, op: PrintWrite...
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)  } fin...
How to defer evaluation?var assertionsEnabled = truedef myAssert(predicate: () => Boolean) =  if (assertionsEnabled && !pr...
Function arguments are              evaluated eagerlydef boolAssert(predicate: Boolean) =  if (assertionsEnabled && !predi...
def byNameAssert(predicate: => Boolean) =  if (assertionsEnabled && !predicate)    throw new AssertionErrorscala> byNameAs...
Re-occurring patterns• transform every element of a list• verify property of all elements of a list• extract elements sati...
Higher-order functions• reducing code duplication, simplifying client codeCurrying• partial function applicationsWriting n...
Polymorphic Type Systems
Lecture 4 type                       Bool x, y;                         x && y                         x || y             ...
Lecture 4type checking: the activity of ensuring thatthe operands of an operation are of compatibletypes.  A compatible ty...
Monomorphic Functionsdef map(xs: IntList, f: Int => Int): IntList = xs match {  case Nil() => Nil()  case Cons(y, ys) => C...
In computer science, polymorphism is a programminglanguage feature that allows values of different data typesto be handled...
Polymorphism  Inclusion polymorphism  Parametric polymorphism  Overloading
Inclusion Polymorphism
In object-oriented programming, subtype         polymorphism or inclusion  polymorphism is a concept in type theory wherei...
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 ...
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 expec...
Liskov Substitution Principle Substitutability is a principle in object-orientedprogramming. It states that, in a computer...
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 sub...
TypePoint = Point(Double x Double)                  + Circle(Doube x Double x Double)                  + Rectangle(Double ...
class BaseTest {                                   public static void main(String[] args) {                               ...
Prototype Inheritance    in JavaScript
objects in JavaScript are dynamically composed without class blueprints     dynamic structural subtyping
In computer programming with object-oriented    programming languages, duck typing is a style ofdynamic typing in which an...
var Duck = function(){    this.quack = function(){alert(Quaaaaaack!);};    this.feathers = function(){alert(The duck has w...
How to organize code reuse?
objects in JavaScript inheritfrom a prototype object
Object.create()   var o1 = Object.create({x:1, y:2});       // o1 inherits properties x and y.   var o2 = Object.create(nu...
var p = {x:1};    // Define a prototype object.var o = Object.create(p);    // Create an object with that prototype.p.isPr...
Inheritancevar o = {}    // o inherits object methods from Object.prototypeo.x = 1;    // and has an own property x.var p ...
var unitcircle = { r:1 };  // An object to inherit fromvar c = Object.create(unitcircle);  // c inherits the property r   ...
function range(from, to) {	 var r = Object.create(range.methods);	 r.from = from;	 r.to   = to;	 return r;                ...
Classes and Constructorsfunction Range(from, to) {	 this.from = from;	 this.to = to;}Range.prototype = {	 includes : funct...
r instanceof Range// returns true if r inherits// from Range.prototype          Constructors and Class Identity
var F = function() {};              Constructor Property  // This is a function object.var p = F.prototype;  // This is th...
function Range(from, to) {	 this.from = from;                     Extend Prototype	 this.to = to;}Range.prototype.includes...
Parametric Polymorphism
If all code is written without mention of any       specific type and thus can be usedtransparently with any number of new ...
A mono-morphic (single-shaped)procedure can operate only on arguments ofa fixed type.A poly-morphic (many-shaped) procedure...
Polymorphic function: parameterized with types                A => B : type of functions from type A to type Bdef map[A,B]...
Overloading
If the function denotes different and potentially heterogeneous implementations depending on a limitedrange of individuall...
Method Overloading       def *(that: Rational): Rational =         new Rational(numer * that.numer, denom * that.denom)   ...
Lab & Exam
Graded Assignment 1  Algebraic datatypes in C  Dynamic dispatch in CImportant dates   Deadline: April 2, 2013 23:59   Exte...
Syntax and Semantics                 Quarter 3  Names, Bindings, and Scopes  Storage  Data Types                       Bas...
Material for exam  Slides from lectures  Tutorial exercises  Sebesta: Chapters 1-3, 5-8  Programming in Scala: Chapters 1,...
Content of exam  20% multiple choice questions  about concepts  40% Scala programming  (functional programming)  20% C pro...
http://department.st.ewi.tudelft.nl/weblab/assignment/850                             Answers will be published when 80   ...
Registration for Exam is Requiredhttp://department.st.ewi.tudelft.nl/weblab/assignment/759 → Your Submission
Good Luck!
Upcoming SlideShare
Loading in …5
×

Ti1220 Lecture 7: Polymorphism

1,543 views

Published on

1 Comment
2 Likes
Statistics
Notes
  • Thanks a lot Mr. Elco Visser!
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
1,543
On SlideShare
0
From Embeds
0
Number of Embeds
971
Actions
Shares
0
Downloads
32
Comments
1
Likes
2
Embeds 0
No embeds

No notes for slide

Ti1220 Lecture 7: Polymorphism

  1. 1. Lecture 6: Polymorphism TI1220 2012-2013 Concepts of Programming Languages Eelco Visser / TU Delft
  2. 2. Robin Milner is generally regarded as havingmade three major contributions to computerscience. He developed LCF, one of the first tools forautomated theorem proving. The language hedeveloped for LCF, ML, was the first language withpolymorphic type inference and type-safeexception handling. In a very different area, Milneralso developed a theoretical framework foranalyzing concurrent systems, the calculus ofcommunicating systems (CCS), and its successor, thepi-calculus. At the time of his death, he was workingon bigraphs, a formalism for ubiquitous computingsubsuming CCS and the pi-calculus.[9] http://en.wikipedia.org/wiki/Robin_Milner
  3. 3. Outline Control abstraction Polymorphism Lab & Exam
  4. 4. Control Abstraction withHigher-Order Functions
  5. 5. Higher-order functions• reducing code duplication, simplifying client codeCurrying• partial function applicationsWriting new control structures• growing the languageBy-name parameters• lazy evaluation
  6. 6. Search for files that end in ... object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesEnding(query: String) = for(file <- filesHere; if file.getName.endsWith(query)) yield file }
  7. 7. Search for files that match a query ...def filesEnding(query: String) = for(file <- filesHere; if file.getName.endsWith(query)) yield filedef filesContaining(query: String) = for(file <- filesHere;if file.getName.contains(query)) yield filedef filesRegex(query: String) = for(file <- filesHere;if file.getName.matches(query)) yield filedef filesMatching(query: String, method) = for(file <- filesHere;if file.getName.method(query)) yield file Find the common pattern
  8. 8. Using a higher-order functiondef 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(_))
  9. 9. Using a higher-order functiondef 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(_))
  10. 10. Using closuresobject 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))}
  11. 11. def containsNeg(nums: List[Int]): Boolean = { var exists = false for (num <- nums) if (num < 0) exists = true Using explicit iteration exists}scala> containsNeg(List(1, 2, 3, 4))res3: Boolean = falsescala> containsNeg(List(1, 2, -3, 4))res5: Boolean = true def containsNeg(nums: List[Int]) = nums.exists(_ < 0)Using higher-order iteration
  12. 12. 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
  13. 13. scala> def plainOldSum(x: Int, y: Int) = x + yplainOldSum: (x: Int,y: Int)Intscala> plainOldSum(1, 2) Curryingres8: Int = 3scala> def curriedSum(x: Int)(y: Int) = x + ycurriedSum: (x: Int)(y: Int)Intscala> curriedSum(1)(2)res9: Int = 3scala> def first(x : Int) = (y : Int) => x + yfirst: (x: Int)(Int) => Intscala> val second = first(1)second: (Int) => Int = <function1>scala> second(2)res12: Int = 3
  14. 14. Curryingscala> val onePlus = curriedSum(1)_onePlus: (Int) => Int = <function1>scala> onePlus(2)res13: Int = 3scala> val twoPlus = curriedSum(2)_twoPlus: (Int) => Int = <function1>scala> twoPlus(2)res14: Int = 4
  15. 15. scala> def twice(op: Double => Double, x: Double) = op(op(x))twice: (op: (Double) => Double, x: Double)Doublescala> twice(_ + 1, 5)res15: Double = 7.0scala> twice((x: Double) => x + 1, 5)res15: Double = 7.0 Making new control structures
  16. 16. Loan Pattern: encapsulate allocation and de-allocation of resourcesdef 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))
  17. 17. scala> println("Hello, world!")Hello, world!scala> println { "Hello, world!" }Hello, world!Curly braces as function call
  18. 18. 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
  19. 19. How to defer evaluation?var assertionsEnabled = truedef myAssert(predicate: () => Boolean) = if (assertionsEnabled && !predicate()) throw new AssertionErrormyAssert(() => 5 > 3)myAssert(5 > 3) // Won’t work, because missing () =>
  20. 20. Function arguments are evaluated eagerlydef 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)
  21. 21. def byNameAssert(predicate: => Boolean) = if (assertionsEnabled && !predicate) throw new AssertionErrorscala> byNameAssert(5 > 3)scala> byNameAssert(10 / 0 == 0) By-name parameters are evaluated on demand
  22. 22. 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 operatorHigher-order functions• direct, reusable definitions of such patterns
  23. 23. Higher-order functions• reducing code duplication, simplifying client codeCurrying• partial function applicationsWriting new control structures• growing the languageBy-name parameters• lazy evaluation
  24. 24. Polymorphic Type Systems
  25. 25. Lecture 4 type Bool x, y; x && y x || y !x operationsdata type: a collection of data values and a set of predefined operations on those values
  26. 26. Lecture 4type checking: the activity of ensuring thatthe operands of an operation are of compatibletypes. 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 Equal limits code reuse
  27. 27. Monomorphic Functionsdef 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)) Repeat pattern for each type
  28. 28. In computer science, polymorphism is a programminglanguage feature that allows values of different data typesto be handled using a uniform interface.The concept of parametric polymorphism applies to bothdata types and functions.A function that can evaluate to or be applied to values ofdifferent 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 designatedpolymorphic data type like the generalized type from whichsuch specializations are made.
  29. 29. Polymorphism Inclusion polymorphism Parametric polymorphism Overloading
  30. 30. Inclusion Polymorphism
  31. 31. 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 areentirely 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)
  32. 32. 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.C: char,int subtype-of long float subtype-of double
  33. 33. 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)
  34. 34. Liskov Substitution Principle Substitutability is a principle in object-orientedprogramming. It states that, in a computer program, if S is a subtype of T, then objects of type T may bereplaced 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.). http://en.wikipedia.org/wiki/Liskov_substitution_principle
  35. 35. 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 substitutability T1 is not a subtype of T2: requires run-time type check
  36. 36. TypePoint = Point(Double x Double) + Circle(Doube x Double x Double) + Rectangle(Double x Double x Double x Double)class Point { protected double x, y; public void draw() { /* ... */ }} Subtype and Inheritance in Javaclass 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);
  37. 37. class BaseTest { public static void main(String[] args) { Base base1 = new Base(45);class Base { Base base2 = new Child(567, 245); Integer x; base1.print(); base2.print(); 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 + ")"); }} Dynamic Dispatch in Java
  38. 38. Prototype Inheritance in JavaScript
  39. 39. objects in JavaScript are dynamically composed without class blueprints dynamic structural subtyping
  40. 40. In computer programming with object-oriented programming languages, duck typing is a style ofdynamic typing in which an objects methods and propertiesdetermine the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface. 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 http://en.wikipedia.org/wiki/Duck_typing
  41. 41. 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); Duck typing in Java Script};game(); http://en.wikipedia.org/wiki/Duck_typing#In_JavaScript
  42. 42. How to organize code reuse?
  43. 43. objects in JavaScript inheritfrom a prototype object
  44. 44. Object.create() 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().
  45. 45. 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 pObject.prototype.isPrototypeOf(p) // => true: p inherits from Object.prototype The prototype Attribute
  46. 46. Inheritancevar 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
  47. 47. var unitcircle = { r:1 }; // An object to inherit fromvar c = Object.create(unitcircle); // c inherits the property r Inheritancec.x = 1; c.y = 1; // c defines two properties of its ownc.r = 2; // c overrides its inherited propertyunitcircle.r; // => 1: the prototype object is not affected
  48. 48. function range(from, to) { var r = Object.create(range.methods); r.from = from; r.to = to; return r; Classes and Prototypes}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)
  49. 49. Classes and Constructorsfunction 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)
  50. 50. r instanceof Range// returns true if r inherits// from Range.prototype Constructors and Class Identity
  51. 51. var F = function() {}; Constructor Property // 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
  52. 52. function Range(from, to) { this.from = from; Extend Prototype 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 + ")";};
  53. 53. Parametric Polymorphism
  54. 54. If all code is written without mention of any specific type and thus can be usedtransparently with any number of new types,it is called parametric polymorphism. http://en.wikipedia.org/wiki/Polymorphism_(computer_science)
  55. 55. A mono-morphic (single-shaped)procedure can operate only on arguments ofa fixed type.A poly-morphic (many-shaped) procedurecan operate uniformly on arguments of awhole family of types.Parametric polymorphism is a typesystem in which we can write polymorphicprocedures.
  56. 56. Polymorphic function: parameterized with types A => B : type of functions from type A to type Bdef 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))
  57. 57. Overloading
  58. 58. If the function denotes different and potentially heterogeneous implementations depending on a limitedrange of individually specified types and combination, it iscalled ad-hoc polymorphism. Ad-hoc polymorphism is supported in many languages using function and method overloading. http://en.wikipedia.org/wiki/Polymorphism_(computer_science)
  59. 59. Method Overloading def *(that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom) def *(i: Int): Rational = new Rational(numer * i, denom)scala> val c = new Rational(3,7)c: Rational = 3/7 In a method call, thescala> c * 2 compiler picks the versionres1: Rational = 6/7 of an overloaded method that correctly matches the types of the arguments.
  60. 60. Lab & Exam
  61. 61. Graded Assignment 1 Algebraic datatypes in C Dynamic dispatch in CImportant dates Deadline: April 2, 2013 23:59 Extension: April 5, 2013 23:59 Submitting after extension date is not possible Maximum penalty for submitting after deadline: 6 points Minimum grade needed: 4 Grade: 70% unit tests, 30% check lists Grade for GAs: average of four assignments
  62. 62. Syntax and Semantics Quarter 3 Names, Bindings, and Scopes Storage Data Types Basics of Functional Programming Scala First-class Functions JavaScript Polymorphism C Type Parameterization Parsing and Interpretation Data Abstraction / Modular Programming Functional Programming Redux Concurrency Concurrent ProgrammingQuarter 4 Domain-Specific Languages
  63. 63. 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
  64. 64. 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)
  65. 65. http://department.st.ewi.tudelft.nl/weblab/assignment/850 Answers will be published when 80 students have completed the exam
  66. 66. Registration for Exam is Requiredhttp://department.st.ewi.tudelft.nl/weblab/assignment/759 → Your Submission
  67. 67. Good Luck!

×