object countUp {
val init = 0 // (Immutable) Field
var count = 0 // (Mutable) Field
def next() = { // Method
count += 1
count
}
def hasNext = true // Method
}
Here's a client function and call. What do you think happens?
scala> def tickN(it: Any, n: Int) =
for (i <- 1 to n) { println(it.next()) }
scala> tickN(countUp, 5)
A. prints out 1 2 3 4 5
with line breaks
B. run-time exception
C. type error at function definition def tickN ...
D. type error at function call tickN(countUp, 5)
Here's a client function and call. What do you think happens?
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())
^
Any
is a supertype of all other types.def tickN(it: ???, n: Int) =
for (i <- 1 to n) { println(it.next()) }
Which members are required by tickN
?
A. count
B. next
C. hasNext
D. next
and hasNext
E. all of the above
def tickN(it: ???, n: Int) =
for (i <- 1 to n) { println(it.next()) }
The mystery type ???
for it
must be an object ...
next
()
and returns anything.{ def next(): Any }
def tickN(it: { def next(): Any }, n: Int) =
for (i <- 1 to n) { println(it.next()) }
Let's take it for a spin.
scala> tickN(countUp, 5)
1
2
3
4
5
scala> def tickN(it: { def next(): Any }, n: Int) =
for (i <- 1 to n) { println(it.next()) }
warning: there were 1 feature warning(s);
re-run with -feature for details
tickN: (it: AnyRef{def next(): Any}, n: Int)Unit
scala -feature
to get more details.AnyRef[T]
?java.lang.Object
of Scala.T
. object countFib {
var a = 0 // Fields
var b = 1
def next() = { // Methods
val (a0, b0) = (b, a + b)
a = a0
b = b0
a
}
def hasNext = true
}
object countFib {
var a = 0; var b = 1
def next() = {
val (a0, b0) = (b, a + b); a = a0; b = b0; a }
def hasNext = true
}
val res = tickN(countFib, 5)
A. prints out 1 1 2 3 5
with line breaks
B. run-time exception
C. type error in def tickN ...
D. type error in tickN(countFib, 5)
tickN
is happy to work on countFib
, too!
scala> tickN(countFib, 5)
1
1
2
3
5
scala> tickN(countFib, 5)
8
13
21
34
55
tickN
doesn't care about extra names and memberstickN
can be called with any object with suitable next
method{ def next(): Any }
tickN
doesn't care about extra names and members
tickN
can be called with any object with suitable next
method
That is, any structural subtype of { def next(): Any }
scala> tickN("yellowmattercustard".iterator, 5)
y
e
l
l
o
scala> tickN(List("cat", "dog").iterator, 5)
cat
dog
java.util.NoSuchElementException: next on empty iterator
tickN
...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())
} else {
println("Out of Stuff!")
}
}
We can add more required methods to tickN
.
Helps to give the new type a name.
type ticker = { def next(): Any; def hasNext: Boolean }
def tickN(it: ticker, n: Int) =
for (i <- 1 to n) {
if (it.hasNext) {
println(it.next())
} else {
println("Out of Stuff!")
}
}
This works quite nicely...
scala> tickN(List("cat", "dog").iterator, 5)
cat
dog
Out of Stuff!
Out of Stuff!
Out of Stuff!
... are simply method calls:
object counter {
var count = 1
def + (i:Int) = count + i
}
scala> counter + 4
res0: Int = 5
scala> counter.+(4)
res1: Int = 5
In fact, all arity-1 methods can be called with infix notation!
object foo {
def bar (i:Int) = i + 1
}
scala> foo.bar(20)
res0: Int = 21
scala> foo bar 20
res1: Int = 21
In Java, C++, C#, you cannot have objects without classes.
But we seem to be doing just fine with standalone objects.
In fact, many OO languages are class-less.
You tell me!
???
???
???
???
Unified mechanism for two tasks:
A name for describing contents
A template for creating new objects
Spectrum from Specification to Implementation
Define a new name for a structural object type.
type ticker = { def next(): Any; def hasNext: Boolean }
class Person(first: String, last: String) {
var alreadyGreeted = false
def greet() =
if (!alreadyGreeted) {
println("Hello, I am %s %s." format (first, last))
alreadyGreeted = true
}
else
println("Hello again.")
}
class
definition.first
and last
are immutable fields (i.e. val
).class Person(first: String, last: String) {
var alreadyGreeted = false
def greet() =
if (!alreadyGreeted) {
println("Hello, I am %s %s." format (first, last))
alreadyGreeted = true
}
else
println("Hello again.")
}
greet
is a method.class Person(first: String, last: String) {
var alreadyGreeted = false
def greet() =
if (!alreadyGreeted) {
println("Hello, I am %s %s." format (first, last))
alreadyGreeted = true
}
else
println("Hello again.")
}
alreadyGreeted
is initialized to false
during construction.Instantiate instances using new
:
scala> var p = new Person("Ravi", "Chugh")
p: Person = Person@9ecad
scala> p.greet()
Hello, I am Ravi Chugh.
scala> p.greet()
Hello again.
To define overloaded constructors:
class Person(first: String, last: String) {
...
def this(f: String) = this(f, "Sapien")
}
Calls the primary constructor.
Often nice to create a standalone object that wraps a class:
object Person {
def apply(first: String, last: String) =
new Person(first, last)
def apply(first: String) =
new Person(first)
}
Now we can create Person
s without new
:
scala> var p = Person("Ravi", "Chugh")
p: Person = Person@9ecad
Often nice to create a standalone object that wraps a class:
object Person {
def apply(first: String, last: String) =
new Person(first, last)
def apply(first: String) =
new Person(first)
}
Single inheritance as in Java using extends
.
class Smith(first: String) extends Person(first, "Smith")
scala> val joe = new Smith("Joe")
joe: Smith = Smith@1c4c573
scala> joe.greet()
Hello, I am Joe Smith.
Can override methods:
class German(first: String, last: String)
extends Person(first, last) {
def override greet() =
println("Hallo, mein name ist %s %s."
format (first, last))
}
override
.scala> new German("Ravi", "Chugh").greet()
Hallo, mein name ist Ravi Chugh.
Can override methods and call superclass methods:
class ShadyPerson(first: String, last: String)
extends Person(first, last) {
override def greet() = {
super.greet()
println("Or am I?")
}
}
scala> new ShadyPerson("Ravi", "Chugh").greet()
Hello, I am Ravi Chugh.
Or am I?
abstract class Iter[A] {
def hasNext: Boolean
def next: A
}
abstract
class can omit implementations...abstract class Iter[A] {
def hasNext: Boolean
def next: A
def maybeNext: Option[A] =
if (hasNext) Some(next) else None
}
abstract
class can omit some implementations...Implement the required functionality in a subclass:
class StrIter(s: String) extends Iter[Char] {
var i = 0
def hasNext = i < s.length()
def next = { val ch = s.charAt(i); i += 1; ch }
}
scala> val iter = new StrIter("hi")
iter: StrIter = StrIter@f17aa1
scala> iter.next
res2: Char = h
scala> iter.next
res3: Char = i
scala> iter.next
java.lang.StringIndexOutOfBoundsException: ...
foreach
?Define a new abstract (sub)class:
abstract class ForIter[A] extends Iter[A] {
def foreach(f: A => Unit) { while (hasNext) f(next) }
}
Define our concrete class to extend ForIter
instead of Iter
:
class ForStrIter(s: String) extends ForIter[Char] {
var i = 0
def hasNext = i < s.length()
def next = { val ch = s.charAt(i); i += 1; ch }
}
Define our concrete class to extend ForIter
instead of Iter
:
class ForStrIter(s: String) extends ForIter[Char] {
var i = 0
def hasNext = i < s.length()
def next = { val ch = s.charAt(i); i += 1; ch }
}
foreach
is inherited as desired.scala> new ForStrIter("hi") foreach println
h
i
Define our concrete class to extend ForIter
instead of Iter
:
class ForStrIter(s: String) extends ForIter[Char] {
var i = 0
def hasNext = i < s.length()
def next = { val ch = s.charAt(i); i += 1; ch }
}
foreach
is inherited as desired.foreach
in ForIter
calls hasNext
and next
from subclass.What if we wanted to define additional operations on Iter
s?
take n
in terms of next
maybeTake n
in terms of maybeNext
What if we wanted to define additional operations on Iter
s?
take n
in terms of next
maybeTake n
in terms of maybeNext
We could define more abstract classes TakeIter
, MaybeTakeIter
, ...
Scala traits are a blend of interfaces, abstract classes, and more.
trait T1 { var i: Int }
trait T2 { var i = 0 }
trait T3 { var i: Int; def f(x: Boolean): Int }
trait T4 { var i: Int; def f(x: Boolean) = i }
trait T5 { def f(x: Boolean): Int }
In most cases, might as well define traits instead of abstract classes:
trait Iter[A] {
def hasNext: Boolean
def next: A
def maybeNext: Option[A] =
if (hasNext) Some(next) else None
}
trait
instead of abstract class
.Concrete classes can extend traits.
Unchanged from before:
class StrIter(s: String) extends Iter[Char] {
var i = 0
def hasNext = i < s.length()
def next = { val ch = s.charAt(i); i += 1; ch }
}
Why is this any better than abstract classes?
Define foreach
in terms of unspecified hasNext
and next
:
trait Foreach[A] {
def hasNext: Boolean
def next: A
def foreach(f: A => Unit) { while (hasNext) f(next) }
}
Define foreach
in terms of unspecified hasNext
and next
:
trait Foreach[A] {
def hasNext: Boolean
def next: A
def foreach(f: A => Unit) { while (hasNext) f(next) }
}
And then combine with (or mix it in to) StrIter
class:
class ForeachStrIter(s: String)
extends StrIter(s) with Foreach[Char]
with
clause.with T1 with T2 ...
).class ForeachStrIter(s: String)
extends StrIter(s) with Foreach[Char]
Works as before:
scala> new ForeachStrIter("hi") foreach println
h
i
Let's take a look at this trait again:
trait Foreach[A] {
def hasNext: Boolean
def next: A
def foreach(f: A => Unit) { while (hasNext) f(next) }
}
Iter
directly...hasNext
and next
) explicitly.Iter
objects of the same shape.Iter
.So we can require that it be mixed in to an Iter
:
trait ForeachIter[A] extends Iter[A] {
def foreach(f: A => Unit) { while (hasNext) f(next) }
}
hasNext
and next
inherited from Iter
.So we can require that it be mixed in to an Iter
:
trait ForeachIter[A] extends Iter[A] {
def foreach(f: A => Unit) { while (hasNext) f(next) }
}
Mix in as before:
scala> (new StrIter("hi") with ForeachIter[Char])
foreach println
h
i
(Single) Inheritance Enables Reuse BUT:
Traits are a powerful take on multiple inheritance.
trait IntQueue { def get(): Int; def put(x: Int) }
class BasicIntQueue extends IntQueue {
private val buf =
new scala.collection.mutable.ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) { buf += x }
}
private
.scala> val q = new BasicIntQueue
q: BasicIntQueue = BasicIntQueue@1ce8b57
scala> q.put(1); q.put(2)
scala> q.get()
res1: Int = 1
scala> q.get()
res2: Int = 2
Say we want to vary how put
behaves:
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2 * x) }
}
Say we want to vary how put
behaves:
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2 * x) }
}
Doubles the new integer and then calls the CORE put
implementation.
scala> val q = new BasicIntQueue with Doubling
q: BasicIntQueue with Doubling = $anon$1@4d4c84
scala> q.put(1); q.put(2)
scala> (q.get(), q.get())
res5: (Int, Int) = (2,4)
Say we want to vary how put
behaves:
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2 * x) }
}
super.put
get resolved? Dynamic dispatch!Doubling
is mixed in after a class that implements put
.abstract override
to indicate this intention.super.put
in an abstract class. Why?trait IntQueue { def get(): Int; def put(x: Int) }
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2 * x) }
}
scala> val q = new IntQueue with Doubling
scala> q.put(1)
scala> val res = (q.qet(), q.qet())
A. 2 : Int
B. (2, 2) : (Int, Int)
C. Run-time Exception
D. Type Error
trait IntQueue { def get(): Int; def put(x: Int) }
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2 * x) }
}
scala> val q = new IntQueue with Doubling
error: object creation impossible, since:
...
method put of type (x: Int)Unit is marked
`abstract' and `override', but no concrete
implementation could be found in a base class
Last class on Tues, Mar 11
trait IntQueue { def get(): Int; def put(x: Int) }
class BasicIntQueue extends IntQueue {
private val buf =
new scala.collection.mutable.ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) { buf += x }
}
Say we want to vary how put
behaves:
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2 * x) } }
Another variation:
trait Incrementing extends IntQueue {
abstract override def put(x: Int) { super.put(x + 1) } }
And another:
trait Filtering extends IntQueue {
abstract override def put(x: Int) {
if (x >= 0) super.put(x) } }
scala> val q = new BasicIntQueue
with Incrementing with Filtering
scala> q.put(-1); q.put(0); q.put(1)
scala> val res = (q.get(), q.get(), q.get())
A. Type Error
B. (-1, 0, 1) : (Int, Int, Int)
C. (0, 1, 2) : (Int, Int, Int)
D. (1, 2) : (Int, Int)
E. Run-time Exception
What happens when mixing multiple traits?
scala> val q = new BasicIntQueue
with Incrementing with Filtering
Filtering.put
Incrementing.put
BasicIntQueue.put
.scala> q.put(-1) // nothing is put
scala> q.put(0) // 1 is put
scala> q.put(1) // 2 is put
scala> q.get() // gets 1
scala> q.get() // gets 2
scala> q.get() // nothing to get!
... exception ...
scala> val q = new BasicIntQueue
with Filtering with Incrementing
scala> q.put(-1); q.put(0); q.put(1)
scala> val res = (q.get(), q.get(), q.get())
A. Type Error
B. (-1, 0, 1) : (Int, Int, Int)
C. (0, 1, 2) : (Int, Int, Int)
D. (1, 2) : (Int, Int)
E. Run-time Exception
(Single) Inheritance Enables Reuse BUT:
Traits are a powerful take on multiple inheritance.
Plenty of online reading if you want to learn more:
Several of the examples today adapted from above.