AWS Community Day CPH - Three problems of Terraform
TI1220 Lecture 8: Traits & Type Parameterization
1. TI1220 2012-2013
Concepts of Programming Languages
Eelco Visser / TU Delft
Lecture 8: Traits & Type Parameterization
2. 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
4. Syntax and Semantics
Names, Bindings, and Scopes
Storage
Data Types
Functional Programming
First-class Functions
Polymorphism
Traits & Type Parameterization
Parsing and Interpretation
Data Abstraction / Modular Programming
Functional Programming Redux
Concurrency
Concurrent Programming
Domain-Specific Languages
Quarter 3
Quarter 4
Basics of
Scala
JavaScript
C
6. 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
7. 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 = ae
val e3: Element = new UniformElement('x', 2, 3)
Subtyping
9. 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
10. 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
11. trait Philosophical {
def philosophize() {
println("I consume memory, therefore I am!")
}
}
class Frog extends Philosophical {
override def toString = "green"
}
scala> val frog = new Frog
frog: Frog = green
scala> frog.philosophize()
I consume memory, therefore I am!
scala> val phil: Philosophical = frog
phil: Philosophical = green
scala> phil.philosophize()
I consume memory, therefore I am!
inheritance: code reuse subtyping: traits are types
Defining and Using Traits
12. class Animal
trait HasLegs
class Frog extends Animal with Philosophical with HasLegs {
override def toString = "green"
override def philosophize() {
println("It ain't easy being " + toString + "!")
}
}
superclass
traits
override code from trait
scala> val phrog: Philosophical = new Frog
phrog: Philosophical = green
scala> phrog.philosophize()
It ain't easy being green!
Mixing in (multiple) traits
13. Trait is like Java interface with
• methods
• fields
• state
Trait is Scala class
• without class parameters
• dynamic binding of ‘super’
14. 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
15. 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
16. 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
17. 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@3536fd
scala> rect.left
res2: Int = 1
scala> rect.right
res3: Int = 10
Rectangular objects with traits
18. 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
19. 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
20. 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
21. 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
22. abstract class IntQueue {
def get(): Int
def put(x: Int)
}
import scala.collection.mutable.ArrayBuffer
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) { buf += x }
}
Class Queue
23. abstract class IntQueue {
def get(): Int
def put(x: Int)
}
import scala.collection.mutable.ArrayBuffer
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) { buf += x }
}
scala> val queue = new BasicIntQueue
queue: 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
24. 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
25. 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 Doubling
defined class MyQueue
scala> val queue = new MyQueue
queue: MyQueue = MyQueue@91f017
scala> queue.put(10)
scala> queue.get()
res12: Int = 20
Trait Doubling
26. 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 Doubling
defined class MyQueue
scala> val queue = new MyQueue
queue: MyQueue = MyQueue@91f017
scala> queue.put(10)
scala> queue.get()
res12: Int = 20
scala> val queue = new BasicIntQueue with Doubling
queue: BasicIntQueue with Doubling = $anon$1@5fa12d
scala> queue.put(10)
scala> queue.get()
res14: Int = 20
Trait Doubling
28. 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 = 1
scala> queue.get()
res15: Int = 2
Stacking Modifications
29. 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 = 1
scala> 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 = 0
scala> queue.get()
res18: Int = 1
scala> queue.get()
res19: Int = 2
30. // Multiple inheritance thought experiment
val q = new BasicIntQueue with Incrementing with Doubling
q.put(42) // which put would be called?
Multiple Inheritance (Why Not?)
Incrementing Doubling
BasicIntQueue
new BasicIntQueue with
Increment with Doubling
31. // Multiple inheritance thought experiment
trait 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!
32. a class is always linearized before all
of its superclasses and mixed in traits
class Animal
trait Furry extends Animal
trait HasLegs extends Animal
trait FourLegged extends HasLegs
class Cat extends Animal with Furry with FourLegged
Linearly Ordering Traits
33. 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
34. 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 -> Animal
b) Cat -> HasLegs -> FourLegged -> Furry -> Animal
c) Cat -> Furry -> FourLegged -> HasLegs -> Animal
d) Cat -> Furry -> HasLegs -> FourLegged -> Animal
Traits Experiment
36. def append[T](xs: List[T], ys: List[T]): List[T] =
xs match {
case List() => ys
case x :: xs1 => x :: append(xs1, ys)
}
37. 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)
}
38. 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
41. 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
42. 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 4
q1: Queue[Int] = Queue(1, 2, 3, 4)
scala> q
res0: Queue[Int] = Queue(1, 2, 3)
Functional Queue
43. 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)
44. 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)
45. 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)
46. 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)
47. 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)
48. 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
49. 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
50. 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)
}
51. 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
52. 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!
53. 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
54. 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
55. 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
56. scala> def doesNotCompile(q: Queue) {}
<console>:5: error: trait Queue takes type parameters
def doesNotCompile(q: Queue) {}
scala> def doesCompile(q: Queue[AnyRef]) {}
doesCompile: (Queue[AnyRef])Unit
Queue is a trait, not a type
Queue is a type constructor or generic trait
Queue[String] is a (specific) type
Generic Traits
57. 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
58. 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] = c1
c2.set(1)
val s: String = c1.get
Covariance and Mutable Classes
59. 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] = c1
c2.set(1)
val s: String = c1.get
Cell.scala:7: error: covariant type T occurs in
contravariant position in type T of value x
def set(x: T) = current = x
Covariance and Mutable Classes
60. 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.
61. // this is Java
String[] a1 = { "abc" };
Object[] a2 = a1;
a2[0] = new Integer(17);
String s = a1[0];
no compile-time error
Variance and Arrays
62. // this is Java
String[] 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
63. // this is Java
String[] 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
64. 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
65. 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 StrangeIntQueue
x.append("abc")
CheckingVariance Annotations
66. 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 StrangeIntQueue
x.append("abc")
Queues.scala:11: error: covariant type T occurs in
contravariant position in type T of value x
def append(x: T) =
ˆ
CheckingVariance Annotations
67. 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 Fruit
class Apple extends Fruit
class 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
68. 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
69. 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
70. 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
71. 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)