Here's a really simple object
object countUp {
private var count = 0 // Data
def next() = { // Function
count += 1
count
}
def hasNext = true // Function
}
- Object = Map from Names To Properties
- Names =
count
,next
,hasNext
- Properties = Data + Functions
Just a fancy value
Here's a client function:
type iterable = { def next(): Any }
scala> def tickN(it: iterable , n: Int) =
for (i <- 1 to n){ println(it.next()) }
scala> tickN(countUp, 5)
What happens?
A. prints out 1 2 3 4 5
B. prints out 1\n 2\n 3\n 4\n 5\n
C. run-time exception
D. type error at function definition def tickN ...
E. type error at function call tickN(countUp, 5)
Here's a client function:
def tickN(it: Any, n: Int) =
for (i <- 1 to n){println(it.next())}
Type checker is not happy.
<console>:9: error: value next is not a member of Any
println(it.next())
^
def tickN(it: Any, n: Int) =
for (i <- 1 to n){println(it.next())}
<console>:9: error: value next is not a member of Any
println(it.next())
^
tickN
with Any
value...Which properties are required by tickN
?
A. count
B. next
C. hasNext
D. next
and hasNext
E. all of the above
The mystery type ???
def tickN(it: ???, n: Int) =
for (i <- 1 to n){println(it.next())}
it
must be an object ...
... that maps the name next
... to a function that takes ()
and returns something.
{ def next(): Any }
- Called a Structural Type
- Describes requirements on structure ...
def tickN(it: { def next(): Any}, n: Int) =
for (i <- 1 to n)
println(it.next())
Thats it!
Lets take it for a spin.
scala> tickN(countUp, 5)
1
2
3
4
5
- Object = Names -> Properties
- Type = Names -> Types
Here's another really simple object
object countFib {
var a = 0 // Data
var b = 1 // Data
def next() = { // Function
val (a0, b0) = (b, a + b)
a = a0
b = b0
a
}
def hasNext = true // Function
}
a
, b
, next
, hasNext
object countFib {
var a = 0 // Field
var b = 1 // Another Field!
def next() = { // Method
val (a1, b1) = (b, a + b)
a = a1
b = b1
a }
def hasNext = true // Another Method!
}
What happens if we try tickN(countFib, 5)
?
A. prints out 1 1 2 3 5
B. prints out 1\n 1\n 2\n 3\n 5\n
C. run-time exception
D. type error in def tickN ...
E. type error in tickN(countFib, 5)
We can use it with tickN
too!
scala> tickN(countFib, 10)
1
2
3
5
8
13
21
34
55
89
tickN
doesn't care about extra names and properties
tickN
can be called with any object with suitable next
{def next():Any}
scala> tickN("yellowmattercustard".iterator, 5)
y
e
l
l
o
tickN
can be called with any object with suitable next
scala> tickN(List("cat", "dog").iterator, 5)
cat
dog
java.util.NoSuchElementException: next on empty iterator
Whoops.
We can add more required methods to tickN
def tickN(it: {def next(): Any; def hasNext: Boolean}, n: Int) =
for (i <- 1 to n){
if (it.hasNext) println(it.next())
}
We can add more required methods to tickN
Better to give the new required type a name
type ticker = { def next(): Any; def hasNext: Boolean }
and then ...
def tickN(it: ticker, n: Int) =
for (i <- 1 to n){
if (it.hasNext) {
println(it.next())
} else {
println("Out of Stuff!")
}
}
Which works quite nicely...
scala> tickN(List("cat", "dog").iterator, 5)
cat
dog
Out of Stuff!
Out of Stuff!
Out of Stuff!
Objects
Maps from names to properties
Properties = Data + Functions (+ Objects)
Types
Short descriptions of Objects
Maps from names to types
Structural Types
What else do we need?
Seem to be doing just fine without them!
Many OO languages are Class-less
- Google's Go
- JavaScript (also type-less ...)
- Java-- (only interfaces)
You tell me!
???
???
???
???
Unified mechanism for two tasks
A name for describing contents
A template for creating new objects
Spectrum from Specification to Implementation
- Only Specification of Types
- Specification + Some Concrete Implementations
- Specification + All Concrete Implementations
type ticker = { def next(): Any; def hasNext: Boolean }
Some other ways too (later.)
Lets make a ScreenSaver!
Here's the basic loop for the graphics:
def run(box: JPanel, points: List[Point]) {
...
while (true) {
for (p <- points) {
p.draw(g)
p.move(d, delta)
p.collide(d)
}
}
...
}
Need a type Point
describing objects that we can:
draw
, move
, collide
Need a type describing draw
, move
, collide
First, some essential fields...
abstract class Point {
// Must be filled in by (concrete) sub-classes
val name : String // immutable
var x : Int // mutable
var y : Int // mutable
// Defined, but may be "overridden" by sub-classes
var xVel = Rand(1, 5)
var yVel = Rand(1, 5)
...
}
Need a type describing draw
, move
, collide
Next, we specify the methods ...
abstract class Point {
// Must be filled in by (concrete) sub-classes
def xDim : Int
def yDim : Int
def render(g: Graphics): Unit
def move(d: Dimension, delta: Int)
...
}
... these are pure specification
... which must be filled in an actual Point
object
Finally, we implement some common methods ...
abstract class Point {
// Can be overridden by concrete Points
def collide(d: Dimension) {
hitWall(x, 0, d.width - xDim) map {(v:Int) =>
x = v
xVel = -xVel
} ...
}
...
}
Finally, we implement some common methods ...
abstract class Point {
// Cannot be overridden by sub-classes
final def draw(g: Graphics) {
val c = g.getColor() // save old color
render(g) // render object
g.setColor(c) // restore old color
}
}
Point
only needs collide
, draw
and move
.Which of the following are type-safe modifications to Point
?
A. Delete the specification of x
and y
B. Delete the specification of collide
C. Delete the implementation of collide
D. (A) and (C)
E. (A), (B) and (C)
Point
?!Like an abstract
class but with everything defined.
class Dot(val name: String, x0: Int, y0: Int) extends Point {
...
}
extends Point
says Dot
is a concrete implementation of Point
.
Parameters define several things "for free"
class Dot(val name: String, x0: Int, y0: Int) extends Point {
// Filled in by "constructor"
var x = x0
var y = y0
def xDim = 20
def yDim = 20
...
}
Generated Constructor
Initializes fields from parameters x0
, y0
Named parameters automatically become fields
We define required methods
class Dot(val name: String, x0: Int, y0: Int) extends Point {
def render(g: Graphics) {
g.fillOval(x, y, xDim, yDim)
}
override def move(d: Dimension, delta: Int) {
x += (delta * xVel)
y += (delta * yVel)
}
}
- Remainder inherited from
Point
Also automatically defined new type ... with name Dot
class Dot(val name: String, x0: Int, y0: Int) extends Point {
def render(g: Graphics) {
g.fillOval(x, y, xDim, yDim)
}
override def move(d: Dimension, delta: Int) {
x += (delta * xVel)
y += (delta * yVel)
}
}
- Called Nominal (by-name) subtyping
- Can use
Dot
wherePoint
is expected
Note the auxiliary constructor this
x0
, y0
?// Auxiliary constructor, invokes "real" constructor
def this(n: String) =
this(n, Rand(0, 100), Rand(0, 100))
With the default constructor ...
new Dot("p1", 50, 50) // Default constructor
... and with the auxiliary one
new Dot("p2") // Auxiliary constructor
class Dot(val name: String, x0: Int, y0: Int) extends Point {
// Filled in by "constructor"
var x = x0
var y = y0
def xDim = 20
def yDim = 20
...
}
Are instances of Dot
A. Mutable
B. Immutable
C. None of the above
Spectrum from Specification to Implementation
Only Specification of Types
e.g. ticker
Specification + Some Concrete Implementations
e.g. Point
Specification + All Concrete Implementations
e.g. Dot
Extend Dot
with a notion of color
class ColorDotV1(name: String, color: Color)
extends Dot(name) {
override def render(g: Graphics) {
g.setColor(color) //"color" in scope from params
super.render(g)
}
}
extends Dot(name)
Pithy way of calling the (old) super-class constructor
Passing it the parameter(s) of the (new) sub-class
class ColorDotV1(name: String, color: Color)
extends Dot(name) {
override def render(g: Graphics) {
g.setColor(color) //"color" in scope from params
super.render(g)
}
}
What has ColorDotV1
inherited ?
A. render
from Point
B. move
from Point
C. render
from Dot
D. move
from Dot
E. All of the above
Lets extend Dot
with a notion of color
class ColorDotV1(name: String, color: Color)
extends Dot(name) {
override def render(g: Graphics) {
g.setColor(color) //"color" in scope from params
super.render(g)
}
}
What does super.render
refer to?
A. render
in Point
B. render
in Dot
C. render
in Surrender
class ColorDotV1(name: String, color: Color)
extends Dot(name) {
override def render(g: Graphics) {
g.setColor(color) //"color" in scope from params
super.render(g)
}
}
The new type ColorDotV1
is a subtype of
A. ticker
B. Point
C. Dot
D. Both Point
and Dot
E. Neither Point
nor Dot
Get lots of properties from without any work!
- Only need to override or extend bits that are different...
How about a Box
?
class Box(name: String, h: Int, w: Int) extends Dot(name) {
override def yDim = w
override def xDim = h
override def render(g: Graphics) {
g.fillRect(x, y, h, w)
}
}
Get lots of stuff without any work!
Get move
(from Dot
) for free!
Get collide
(from Point
) for free!
How about a Box
?
Lets check it out ...
val points = List( ...
, new Box("p4", 20, 40)
)
- Good Angel: Is a
Box
really aDot
?- Evil Angel: But we're getting so much reuse !!
- Good Angel: ...
How about we color them Box
es?
- But how?
- Inheritance?
- Lets see...
Lets try extending Box
...
class ColorBoxV1(name: String, color: Color, h: Int, w: Int)
extends Box(name, h: Int, w: Int) {
override def render(g: Graphics) {
g.setColor(color)
super.render(g)
}
}
... Yikes! We're repeating ourselves!
render
is just likeColorDot.render
- How can we reuse the above
render
?
No problem, just make ColorBox
inherit from (extend) ColorDot
.
Whats the problem?
A. Cannot extend ColorDot
B. Would have to duplicate render
method from Dot
C. Don't fuss, THERE IS NO PROBLEM!
D. Would have to duplicate render
method from Box
E. Would have to duplicate constructor from Box
We bundled Colored-ness into ColorDot
...
... but color is totally independent of Shape!
How can we free up colored-ness from its chains?
Describe object behaviors independent particular classes ...
... Can be mixed into any class to add that behavior to the class!
trait Colored extends Point {
val color: Color // Will be defined by mixin-target
abstract override def render(g: Graphics) {
g.setColor(color)
super.render(g) // Call render of mixin-target
}
}
This exactly specifies and implements colored-ness ...
... without bundling it inside a particular class!
trait Colored extends Point {
val color: Color // Will be defined by mixin-target
abstract override def render(g: Graphics) {
g.setColor(color)
super.render(g) // Call render of mixin-target
}
}
trait Colored extends Point {
val color: Color // Will be defined by mixin-target
abstract override def render(g: Graphics) {
g.setColor(color)
super.render(g) // Call render of mixin-target
}
}
Why do we write extends Point
?
A. Good documentation, we don't have to write it
B. Only Point
objects can have a color
field
C. Only Point
objects can have a setColor
method
D. Both B and C
E. Only Point
objects can have a render
method
To use a trait, just mixin to an appropriate host class.
(e.g. subtype of Point
)
class ColorDot(name: String, val color: Color)
extends Dot(name) with Colored
class ColorBox(name: String, val color: Color, h: Int, w: Int)
extends Box(name, h, w) with Colored
Lets take the new classes for a spin!
Describe object behaviors independent particular classes ...
... Can be mixed into any class to add that behavior to the class!
Why can't I do something like this:
abstract class Point
class Dot extends Point {...}
class Colored extends Point {...}
and then
class ColoredDot extends Dot, Colored {...}
Wouldn't this solve the problem of reusing Color
and Dot
?
- Problem 1: Which parent do I pick method from?
- Problem 2: Need to compose
render
from both
The cunning thing about traits:
trait Colored extends Point {
val color: Color // Will be defined by mixin-target
abstract override def render(g: Graphics) {
g.setColor(color)
super.render(g) // Call render of mixin-target
}
}
- stacks the new behavior
- on top of existing behavior! (
super.render
)
- independent of the host class!
- So, color-a-box, color-a-dot, color-anything!
Lets add a notion of gravity to the objects...
trait Bouncing extends Point {
...
abstract override def move(d: Dimension, delta: Int) {
yVel += delta
shootUp() // if ROTFL
super.move(d, delta) // preserve
}
}
Lets add a gravity behavior to the objects...
- independent of shape, color, etc.
trait Bouncing extends Point {
...
abstract override def move(d: Dimension, delta: Int) {
yVel += delta
shootUp() // if ROTFL
super.move(d, delta) // preserve
}
}
super.move
We can add bouncing behavior to any Point
object
class BouncingColorDot(name: String, val color: Color)
extends Dot(name) with Colored with Bouncing
class BouncingColorBox(name: String, val color: Color, h: Int, w: Int)
extends Box(name, h, w) with Colored with Bouncing
Or even directly retro-actively to the instance object
new Dot("p2") with Bouncing
Inheritance enables reuse
BUT
Class(ical) inheritance locks reusable behaviors
Into unrelated classes (e.g. ColorDotV1
)
In fact ...
There remains another accidental intertwining...
...behavior locked inside unrelated class.
Where?
A. collide
B. move
C. color
D. Dot
E. Box
Inheritance enables reuse
BUT
Class(ical) inheritance locks reusable behaviors
Into unrelated classes (e.g. ColorDotV1
)
SO
Keep a sharp eye out for independent behaviors,
and set them free using traits.