Post on 18-May-2015
TI1220 2012-2013Concepts of Programming Languages
Eelco Visser / TU Delft
Lecture 8: Traits & Type Parameterization
var ms := course.managers;
var ms : Set<Person> := Set<Person>();ms.addAll(course.managers);
Analysis => Lecture 12: Concurrency
Root Cause
The Fix
Intention: copy semantics
Effect: reference semantics
The Fault: Concurrent writes on CourseEdition
The Airconditioning
Syntax and SemanticsNames, Bindings, and Scopes Storage Data TypesFunctional ProgrammingFirst-class FunctionsPolymorphism
Traits & Type ParameterizationParsing and InterpretationData Abstraction / Modular ProgrammingFunctional Programming ReduxConcurrencyConcurrent ProgrammingDomain-Specific Languages
Quarter 3
Quarter 4
Basics ofScalaJavaScriptC
Traits
abstract class Element { def contents: Array[String] def height: Int = contents.length def width: Int = if (height == 0) 0 else contents(0).length}
class ArrayElement(conts: Array[String]) extends Element { val contents: Array[String] = conts}
Classical Inheritance
Inheriting fields and methods
class UniformElement( ch: Char, override val width: Int, override val height: Int) extends Element { private val line = ch.toString * width def contents = Array.make(height, line)}
val e1: Element = new ArrayElement(Array("hello", "world"))val ae: ArrayElement = new LineElement("hello")val e2: Element = aeval e3: Element = new UniformElement('x', 2, 3)
Subtyping
abstract class Element { def demo() { println("Element's implementation invoked") }}class ArrayElement extends Element { override def demo() { println("ArrayElement's implementation invoked") }}class LineElement extends ArrayElement { override def demo() { println("LineElement's implementation invoked") }}// UniformElement inherits Element’s democlass UniformElement extends Element
def invokeDemo(e: Element) { e.demo()}scala> invokeDemo(new ArrayElement)ArrayElement's implementation invoked
scala> invokeDemo(new LineElement)LineElement's implementation invoked
scala> invokeDemo(new UniformElement)Element's implementation invoked
Dynamic Binding
Subtyping
• Polymorphism & dynamic binding
Code Reuse
• reuse instance variables and methods from super class
Single Inheritance
• cannot reuse code from more than one class
Interfaces
• support subtyping multiple classes
• must re-implement interface
Java-style Single Inheritance
Trait
• reusable unit of code
• encapsulates method and field definitions
• reused by mixing into classes
• class can mix in any number of traits
Applications
• rich interfaces
• stackable modifications
trait Philosophical { def philosophize() { println("I consume memory, therefore I am!") }}class Frog extends Philosophical { override def toString = "green"}
scala> val frog = new Frogfrog: Frog = greenscala> frog.philosophize()I consume memory, therefore I am!
scala> val phil: Philosophical = frogphil: Philosophical = greenscala> phil.philosophize()I consume memory, therefore I am!
inheritance: code reuse subtyping: traits are types
Defining and Using Traits
class Animaltrait HasLegsclass Frog extends Animal with Philosophical with HasLegs { override def toString = "green" override def philosophize() { println("It ain't easy being " + toString + "!") }}
superclasstraits
override code from traitscala> val phrog: Philosophical = new Frogphrog: Philosophical = greenscala> phrog.philosophize()It ain't easy being green!
Mixing in (multiple) traits
Trait is like Java interface with
• methods
• fields
• state
Trait is Scala class
• without class parameters
• dynamic binding of ‘super’
Rich interface
• many methods
• convenient for client
• more work for implementer
Thin interface
• few methods
• easy for implementers
• inconvenient for client
trait CharSequence { def charAt(index: Int): Char def length: Int def subSequence(start: Int, end: Int): CharSequence def toString(): String}
Rich Interfaces with Traits
• small number of abstract methods implemented by client
• large number of concrete methods inherited by client
class Point(val x: Int, val y: Int)class Rectangle(val topLeft: Point, val bottomRight: Point) { def left = topLeft.x def right = bottomRight.x def width = right - left // and many more geometric methods...}
abstract class Component { def topLeft: Point def bottomRight: Point def left = topLeft.x def right = bottomRight.x def width = right - left // and many more geometric methods...}
Rectangular objects without traits
trait Rectangular { def topLeft: Point def bottomRight: Point def left = topLeft.x def right = bottomRight.x def width = right - left // and many more geometric methods...}
abstract class Component extends Rectangular { // other methods...}
class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular { // other methods...}
Rectangular objects with traits
trait Rectangular { def topLeft: Point def bottomRight: Point def left = topLeft.x def right = bottomRight.x def width = right - left // and many more geometric methods...}
abstract class Component extends Rectangular { // other methods...}
class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular { // other methods...}
scala> val rect = new Rectangle(new Point(1, 1), new Point(10, 10))rect: Rectangle = Rectangle@3536fdscala> rect.leftres2: Int = 1scala> rect.rightres3: Int = 10
Rectangular objects with traits
class Rational(n: Int, d: Int) { // ... def <(that: Rational) = this.numer * that.denom > that.numer * this.denom def >(that: Rational) = that < this def <=(that: Rational) = (this < that) || (this == that) def >=(that: Rational) = (this > that) || (this == that)}
defined in terms of < based on standard
semantics of ordering
{
A Rich Interface for Ordering
class Rational(n: Int, d: Int) { // ... def <(that: Rational) = this.numer * that.denom > that.numer * this.denom def >(that: Rational) = that < this def <=(that: Rational) = (this < that) || (this == that) def >=(that: Rational) = (this > that) || (this == that)}
defined in terms of < based on standard
semantics of ordering
{
class Rational(n: Int, d: Int) extends Ordered[Rational] { // ... def compare(that: Rational) = (this.numer * that.denom) - (that.numer * this.denom)}
A Rich Interface for Ordering
class Rational(n: Int, d: Int) { // ... def <(that: Rational) = this.numer * that.denom > that.numer * this.denom def >(that: Rational) = that < this def <=(that: Rational) = (this < that) || (this == that) def >=(that: Rational) = (this > that) || (this == that)}
defined in terms of < based on standard
semantics of ordering
{
class Rational(n: Int, d: Int) extends Ordered[Rational] { // ... def compare(that: Rational) = (this.numer * that.denom) - (that.numer * this.denom)}
Ordered trait provides reusable implementation of ordering
A Rich Interface for Ordering
Class Queue of integers
• put: place integer in queue
• get: take integer out
• first-in first-out
Modifications
• Doubling: double all integers put in queue
• Incrementing: increment all integers put in queue
• Filtering: filter out negative
Stackable Modifications
abstract class IntQueue { def get(): Int def put(x: Int)}
import scala.collection.mutable.ArrayBufferclass BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x }}
Class Queue
abstract class IntQueue { def get(): Int def put(x: Int)}
import scala.collection.mutable.ArrayBufferclass BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x }}
scala> val queue = new BasicIntQueuequeue: BasicIntQueue = BasicIntQueue@24655f
scala> queue.put(10)
scala> queue.put(20)
scala> queue.get()res9: Int = 10
scala> queue.get()res10: Int = 20
Class Queue
trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(2 * x) }}
dynamically bound
can only be mixed into subclasses of IntQueue
mix into class with concrete
definition
Trait Doubling
trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(2 * x) }}
dynamically bound
can only be mixed into subclasses of IntQueue
mix into class with concrete
definition
scala> class MyQueue extends BasicIntQueue with Doublingdefined class MyQueue
scala> val queue = new MyQueuequeue: MyQueue = MyQueue@91f017
scala> queue.put(10)
scala> queue.get()res12: Int = 20
Trait Doubling
trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(2 * x) }}
dynamically bound
can only be mixed into subclasses of IntQueue
mix into class with concrete
definition
scala> class MyQueue extends BasicIntQueue with Doublingdefined class MyQueue
scala> val queue = new MyQueuequeue: MyQueue = MyQueue@91f017
scala> queue.put(10)
scala> queue.get()res12: Int = 20
scala> val queue = new BasicIntQueue with Doublingqueue: BasicIntQueue with Doubling = $anon$1@5fa12d
scala> queue.put(10)
scala> queue.get()res14: Int = 20
Trait Doubling
trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) }}trait Filtering extends IntQueue { abstract override def put(x: Int) { if (x >= 0) super.put(x) }}
Stacking Modifications
trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) }}trait Filtering extends IntQueue { abstract override def put(x: Int) { if (x >= 0) super.put(x) }}
scala> val queue = (new BasicIntQueue with Incrementing with Filtering)scala> queue.put(-1); queue.put(0); queue.put(1)
scala> queue.get()res15: Int = 1scala> queue.get()res15: Int = 2
Stacking Modifications
trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) }}trait Filtering extends IntQueue { abstract override def put(x: Int) { if (x >= 0) super.put(x) }}
scala> val queue = (new BasicIntQueue with Incrementing with Filtering)scala> queue.put(-1); queue.put(0); queue.put(1)
scala> queue.get()res15: Int = 1scala> queue.get()res15: Int = 2
Stacking Modifications
scala> val queue = (new BasicIntQueue with Filtering with Incrementing)scala> queue.put(-1); queue.put(0); queue.put(1)
scala> queue.get()res17: Int = 0scala> queue.get()res18: Int = 1scala> queue.get()res19: Int = 2
// Multiple inheritance thought experimentval q = new BasicIntQueue with Incrementing with Doublingq.put(42) // which put would be called?
Multiple Inheritance (Why Not?)
Incrementing Doubling
BasicIntQueue
new BasicIntQueue with Increment with Doubling
// Multiple inheritance thought experimenttrait MyQueue extends BasicIntQueue with Incrementing with Doubling { def put(x: Int) { Incrementing.super.put(x) // (Not real Scala) Doubling.super.put(x) }}
Multiple Inheritance (Why Not?)
Incrementing Doubling
BasicIntQueue
new BasicIntQueue with Increment with Doubling
put of BasicIntQue called twice!
a class is always linearized before all of its superclasses and mixed in traits
class Animaltrait Furry extends Animaltrait HasLegs extends Animaltrait FourLegged extends HasLegsclass Cat extends Animal with Furry with FourLegged
Linearly Ordering Traits
Units of code
• reusable through inheritance
• can be mixed in at multiple places in hierarchy
Multiple inheritance ++
• calls to super are linearized
• avoid diamond problem
• stack changes
Traits Summary
What is the value of question in:
class Animal { override def toString = "Animal"}trait Furry extends Animal { override def toString = "Furry -> " + super.toString}trait HasLegs extends Animal { override def toString = "HasLegs -> " + super.toString}trait FourLegged extends HasLegs { override def toString = "FourLegged -> " + super.toString}class Cat extends Animal with Furry with FourLegged { override def toString = "Cat -> " + super.toString}val question = new Cat
a) Cat -> FourLegged -> HasLegs -> Furry -> Animalb) Cat -> HasLegs -> FourLegged -> Furry -> Animalc) Cat -> Furry -> FourLegged -> HasLegs -> Animald) Cat -> Furry -> HasLegs -> FourLegged -> Animal
Traits Experiment
Type Parameterization
def append[T](xs: List[T], ys: List[T]): List[T] = xs match { case List() => ys case x :: xs1 => x :: append(xs1, ys) }
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)}
Generic classes and traits
• Set[T]: generic sets parameterized with type T
• Set[Int]: set of integers, instance of Set[T]
• No raw types: always use with type parameter
Example: Functional Queues
typedef struct queue_elem { int val; struct queue_elem *next;} queue_elem;
typedef struct queue { queue_elem *first; queue_elem *last;} queue;
queue *newQueue() { queue *q = (queue *)malloc(sizeof(queue)); q->first = NULL; q->last = NULL; return q;}
Imperative Queue in C
void enqueue(queue *q, int val) { queue_elem *elem = (queue_elem *)malloc(sizeof(queue_elem)); elem->val = val; elem->next = NULL; if(q->first == NULL) { q->first = q->last = elem; } else { q->last->next = elem; q->last = elem; }}
Imperative Queue in C
int dequeue(queue *q) { if(q == NULL || q->first == NULL) { return 0; } int val = q->first->val; queue_elem *elem = q->first; if(q->first == q->last) { q->last = NULL; } q->first = q->first->next; free(elem); return val;}
Imperative Queue in C
Queue operations
• head: return first element
• tail: return rest
• append: new queue with new element at the end
Functional Queue
• fully persistent
• contents not changed when appending
• efficient implementation should be O(1) for all operations
scala> val q = Queue(1, 2, 3)q: Queue[Int] = Queue(1, 2, 3)
scala> val q1 = q append 4q1: Queue[Int] = Queue(1, 2, 3, 4)
scala> qres0: Queue[Int] = Queue(1, 2, 3)
Functional Queue
class SlowAppendQueue[T](elems: List[T]) { // Not efficient def head = elems.head def tail = new SlowAppendQueue(elems.tail) def append(x: T) = new SlowAppendQueue(elems ::: List(x))}
Functional Queue (First Attempt)
class SlowAppendQueue[T](elems: List[T]) { // Not efficient def head = elems.head def tail = new SlowAppendQueue(elems.tail) def append(x: T) = new SlowAppendQueue(elems ::: List(x))}
append = O(n)
Functional Queue (First Attempt)
class SlowAppendQueue[T](elems: List[T]) { // Not efficient def head = elems.head def tail = new SlowAppendQueue(elems.tail) def append(x: T) = new SlowAppendQueue(elems ::: List(x))}
class SlowHeadQueue[T](smele: List[T]) { // Not efficient // smele is elems reversed def head = smele.last def tail = new SlowHeadQueue(smele.init) def append(x: T) = new SlowHeadQueue(x :: smele)}
append = O(n)
Functional Queue (First Attempt)
class SlowAppendQueue[T](elems: List[T]) { // Not efficient def head = elems.head def tail = new SlowAppendQueue(elems.tail) def append(x: T) = new SlowAppendQueue(elems ::: List(x))}
class SlowHeadQueue[T](smele: List[T]) { // Not efficient // smele is elems reversed def head = smele.last def tail = new SlowHeadQueue(smele.init) def append(x: T) = new SlowHeadQueue(x :: smele)}
append = O(n)
head, tail = O(n)
Functional Queue (First Attempt)
class SlowAppendQueue[T](elems: List[T]) { // Not efficient def head = elems.head def tail = new SlowAppendQueue(elems.tail) def append(x: T) = new SlowAppendQueue(elems ::: List(x))}
class SlowHeadQueue[T](smele: List[T]) { // Not efficient // smele is elems reversed def head = smele.last def tail = new SlowHeadQueue(smele.init) def append(x: T) = new SlowHeadQueue(x :: smele)}
append = O(n)
head, tail = O(n)
head, tail, append = O(1) cannot be possible!
Functional Queue (First Attempt)
class Queue[T]( private val leading: List[T], private val trailing: List[T]) { def head = leading.head def tail = new Queue(leading.tail, trailing) def append(x: T) = new Queue(leading, x :: trailing)}
elems == leading ::: trailing.reverse
Represent Queue with Two Lists
class Queue[T]( private val leading: List[T], private val trailing: List[T]) { def head = leading.head def tail = new Queue(leading.tail, trailing) def append(x: T) = new Queue(leading, x :: trailing)}
elems == leading ::: trailing.reverse
but what if leading.isEmpty?
Represent Queue with Two Lists
Mirroring
class Queue[T]( private val leading: List[T], private val trailing: List[T]) { private def mirror = if (leading.isEmpty) new Queue(trailing.reverse, Nil) else this def head = mirror.leading.head def tail = { val q = mirror new Queue(q.leading.tail, q.trailing) } def append(x: T) = new Queue(leading, x :: trailing)}
Mirroring
class Queue[T]( private val leading: List[T], private val trailing: List[T]) { private def mirror = if (leading.isEmpty) new Queue(trailing.reverse, Nil) else this def head = mirror.leading.head def tail = { val q = mirror new Queue(q.leading.tail, q.trailing) } def append(x: T) = new Queue(leading, x :: trailing)}
head, tail, append: O(1)mirror: O(n) but amortized over n calls of tail
Mirroring
class Queue[T]( private val leading: List[T], private val trailing: List[T]) { private def mirror = if (leading.isEmpty) new Queue(trailing.reverse, Nil) else this def head = mirror.leading.head def tail = { val q = mirror new Queue(q.leading.tail, q.trailing) } def append(x: T) = new Queue(leading, x :: trailing)}
head, tail, append: O(1)mirror: O(n) but amortized over n calls of tail
implementation is exposed!
class Queue[T] private ( private val leading: List[T], private val trailing: List[T]) { def this() = this(Nil, Nil) def this(elems: T*) = this(elems.toList, Nil)
def head = ... def tail = ... def append(x: T) = ...}
scala> Queue(1, 2, 3)
private parameters
public auxiliary constructors
hide implementation details from clients
Private Constructors
class Queue[T] private ( private val leading: List[T], private val trailing: List[T]) { def head = ... def tail = ... def append(x: T) = ... }
object Queue { // constructs a queue with initial elements ‘xs’ def apply[T](xs: T*) = new Queue[T](xs.toList, Nil)}
Factory Method hide implementation details from clients
factory method
private parameters
trait Queue[T] { def head: T def tail: Queue[T] def append(x: T): Queue[T]}
object Queue { def apply[T](xs: T*): Queue[T] = new QueueImpl[T](xs.toList, Nil)
private class QueueImpl[T]( private val leading: List[T], private val trailing: List[T] ) extends Queue[T] { def mirror = if (leading.isEmpty) new QueueImpl(trailing.reverse, Nil) else this def head: T = mirror.leading.head def tail: QueueImpl[T] = { val q = mirror new QueueImpl(q.leading.tail, q.trailing) } def append(x: T) = new QueueImpl(leading, x :: trailing) }
}
hide implementation details from clients
scala> def doesNotCompile(q: Queue) {}<console>:5: error: trait Queue takes type parametersdef doesNotCompile(q: Queue) {}
scala> def doesCompile(q: Queue[AnyRef]) {}doesCompile: (Queue[AnyRef])Unit
Queue is a trait, not a typeQueue is a type constructor or generic trait
Queue[String] is a (specific) type
Generic Traits
Queue[String] subtype of Queue[AnyRef] ?
if S subtype of T then Queue[S] subtype of Queue[T] ?
If answer is yes: Queue is covariant in T
trait Queue[+T] { ... }
val q: Queue[AnyRef] = Queue[String](“a”)
If answer is no: Queue is contravariant in T
trait Queue[-T] { ... }
val q: Queue[String] = Queue[AnyRef]() default: nonvariant
Subtyping & Variance Annotations
class Cell[+T](init: T) { private[this] var current = init def get = current def set(x: T) { current = x }}
val c1 = new Cell[String]("abc")val c2: Cell[Any] = c1c2.set(1)val s: String = c1.get
Covariance and Mutable Classes
class Cell[+T](init: T) { private[this] var current = init def get = current def set(x: T) { current = x }}
val c1 = new Cell[String]("abc")val c2: Cell[Any] = c1c2.set(1)val s: String = c1.get
Cell.scala:7: error: covariant type T occurs incontravariant position in type T of value xdef set(x: T) = current = x
Covariance and Mutable Classes
package society { package professional { class Executive { private[professional] var workDetails = null private[society] var friends = null private[this] var secrets = null
def help(another : Executive) { println(another.workDetails) println(another.secrets) //ERROR } } }}
Source: http://www.tutorialspoint.com/scala/scala_access_modifiers.htm
Scope of Protection of Access Modifiers
A private member is visible only inside the class or object that contains the member definition.
A protected member is only accessible from subclasses of the class in which the member is defined.
Every member not labeled private or protected is public. There is no explicit modifier for public members. Such members can be accessed from anywhere.
// this is JavaString[] a1 = { "abc" };Object[] a2 = a1;a2[0] = new Integer(17);String s = a1[0];
no compile-time error
Variance and Arrays
// this is JavaString[] a1 = { "abc" };Object[] a2 = a1;a2[0] = new Integer(17);String s = a1[0];
Exception in thread "main" java.lang.ArrayStoreException:java.lang.Integer at JavaArrays.main(JavaArrays.java:8)
no compile-time error
Variance and Arrays
// this is JavaString[] a1 = { "abc" };Object[] a2 = a1;a2[0] = new Integer(17);String s = a1[0];
Exception in thread "main" java.lang.ArrayStoreException:java.lang.Integer at JavaArrays.main(JavaArrays.java:8)
no compile-time error
motivation: generic treatment of arrays:
void sort(Object[] a, Comparator cmp) { ... }
Variance and Arrays
scala> val a1 = Array("abc")a1: Array[java.lang.String] = Array(abc)
scala> val a2: Array[Any] = a1<console>:5: error: type mismatch; found : Array[java.lang.String] required: Array[Any] val a2: Array[Any] = a1 ˆ
scala> val a2: Array[Object] = a1.asInstanceOf[Array[Object]]a2: Array[java.lang.Object] = Array(abc)
Scala Arrays are Non-variant
class Queue[+T] { def append(x: T) = ...}
class StrangeIntQueue extends Queue[Int] { override def append(x: Int) = { println(Math.sqrt(x)) super.append(x) }}
val x: Queue[Any] = new StrangeIntQueuex.append("abc")
Checking Variance Annotations
class Queue[+T] { def append(x: T) = ...}
class StrangeIntQueue extends Queue[Int] { override def append(x: Int) = { println(Math.sqrt(x)) super.append(x) }}
val x: Queue[Any] = new StrangeIntQueuex.append("abc")
Queues.scala:11: error: covariant type T occurs incontravariant position in type T of value xdef append(x: T) = ˆ
Checking Variance Annotations
class Queue[+T]( private val leading: List[T], private val trailing: List[T]) { def append[U >: T](x: U) = new Queue[U](leading, x :: trailing) // ...}class Fruitclass Apple extends Fruitclass Orange extends Fruit
scala> val qa = Queue(new Apple)scala> val qb = qa.append(new Orange)qb: Queue[Fruit] = ...
U >: T == U is a supertype of T
Lower Bounds
class Queue[+T] private ( private[this] var leading: List[T], private[this] var trailing: List[T]) { private def mirror() = if (leading.isEmpty) { while (!trailing.isEmpty) { leading = trailing.head :: leading trailing = trailing.tail } } def head: T = { mirror(); leading.head } def tail: Queue[T] = { mirror(); new Queue(leading.tail, trailing) } def append[U >: T](x: U) = new Queue[U](leading, x :: trailing)}
Optimized Functional Queue
def orderedMergeSort[T <: Ordered[T]](xs: List[T]): List[T] = { def merge(xs: List[T], ys: List[T]): List[T] = (xs, ys) match { case (Nil, _) => ys case (_, Nil) => xs case (x :: xs1, y :: ys1) => if (x < y) x :: merge(xs1, ys) else y :: merge(xs, ys1) } val n = xs.length / 2 if (n == 0) xs else { val (ys, zs) = xs splitAt n merge(orderedMergeSort(ys), orderedMergeSort(zs)) }}
Upperbounds
Information hiding
• private constructors
• factory methods
• object private members
Type variance
• subtyping of generic types
• covariant, contravariant variance annotations
• lower bounds, upper bounds
Type Parameterization Summary
Reading & Programming in Week 6
Reading
Scala Chapter 12: Traits
Scala Chapter 19: Type Parameterization
Week 9: Parsers and Interpreters
WebLab: Graded Assignment 2: (deadline 14 May 2013, 23:59)