News

Homework 6, Due: Fri Mar 14

Problem 1: Binary Search Trees

Problem 2: Function Decorators

Problems 3 and 4: Serialization

ScalaCheck

What is an Object?

A Really Simple Object

object countUp {
  val init  = 0         // (Immutable) Field
  var count = 0         // (Mutable) Field
  
  def next() = {        // Method
    count += 1 
    count 
  }

  def hasNext = true    // Method
}

How Do We USE an Object?

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)

How Do We USE an Object?

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())
                       ^

CLICKER: How to DESCRIBE the Object?

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

Describing Objects with "Structural" Types

def tickN(it: ???, n: Int) = 
   for (i <- 1 to n) { println(it.next()) }

The mystery type ??? for it must be an object ...

{ def next(): Any }

Describing Objects with "Structural" Types

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

Two Footnotes

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
  1. What's this warning ?
  1. What's AnyRef[T] ?

Recap

Another Really Simple Object

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
}

CLICKER: What Happens... ?

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)

Structural Object Types

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

Structural Object Types

Structural Object Types

scala> tickN("yellowmattercustard".iterator, 5)
y
e
l
l
o

Structural Object Types

scala> tickN(List("cat", "dog").iterator, 5)

cat
dog

java.util.NoSuchElementException: next on empty iterator

Structural Object Types

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!")
    }
  } 

Structural Object Types

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!")
    }
  } 

Structural Object Types

This works quite nicely...

scala> tickN(List("cat", "dog").iterator, 5)
cat
dog
Out of Stuff!
Out of Stuff!
Out of Stuff!

"Operators"...

... 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

Infix Notation

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

Recap

Objects... What About Classes?

What is a Class Anyway?

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.

So What Good are Classes?

You tell me!

What is a Class?

Unified mechanism for two tasks:

Specification

A name for describing contents

Implementation

A template for creating new objects

What is a Class?

Spectrum from Specification to Implementation

1. Interfaces (as in Java)

2. Abstract Class

3. (Concrete) Class

"Interfaces" in Scala

"Interfaces" in Scala

Define a new name for a structural object type.

type ticker = { def next(): Any; def hasNext: Boolean }

Concrete Classes

A Simple Class

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.")
}

A Simple Class

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.")
}

A Simple Class

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.")
}

A Simple Class

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.

Auxiliary Constructors

To define overloaded constructors:

class Person(first: String, last: String) {
  ...
  def this(f: String) = this(f, "Sapien")
}

Calls the primary constructor.

Companion Objects

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 Persons without new:

scala> var p = Person("Ravi", "Chugh")
p: Person = Person@9ecad

Companion Objects

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)
}

Subclassing

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.

Subclassing

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))
}
scala> new German("Ravi", "Chugh").greet()
Hallo, mein name ist Ravi Chugh.

Subclassing

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?

That's Concrete Classes...

Let's Start Separating
Specification from Implementation

Abstract Classes

Abstract Classes

abstract class Iter[A] {
  def hasNext: Boolean
  def next: A


}

Abstract Classes

abstract class Iter[A] {
  def hasNext: Boolean
  def next: A
  def maybeNext: Option[A] =
    if (hasNext) Some(next) else None
}

Simple String Iterator

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 }
}

Simple String Iterator

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: ...

What if we want to add foreach?

Where should we put it?

Extend Class Hierarchy

Define a new abstract (sub)class:

abstract class ForIter[A] extends Iter[A] {
  def foreach(f: A => Unit) { while (hasNext) f(next) }
}

Extend Class Hierarchy

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 }
}

Extend Class Hierarchy

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 }
}
scala> new ForStrIter("hi") foreach println
h
i

Extend Class Hierarchy

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 }
}

Adding More Functionality

What if we wanted to define additional operations on Iters?

Adding More Functionality

What if we wanted to define additional operations on Iters?

We could define more abstract classes TakeIter, MaybeTakeIter, ...

It Would Be Nice to Mix and Match...

Traits

Traits

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 }

Traits

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
}

Traits

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 }
}

Traits

Why is this any better than abstract classes?

Foreach Trait (First Version)

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) }
}

Foreach Trait (First Version)

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]

Foreach Trait (First Version)

class ForeachStrIter(s: String)
extends StrIter(s) with Foreach[Char]

Works as before:

scala> new ForeachStrIter("hi") foreach println
h
i

Foreach Trait (First Version)

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) }
}

Foreach Trait (Second Version)

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) }
}

Foreach Trait (Second Version)

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

Recap

(Single) Inheritance Enables Reuse BUT:

Traits are a powerful take on multiple inheritance.

Stacking Multiple Traits

Typical Hierarchy in Scala

Integer Queues

BASE Specification

trait IntQueue { def get(): Int; def put(x: Int) } 

CORE Functionality

class BasicIntQueue extends IntQueue {
  private val buf =
    new scala.collection.mutable.ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) { buf += x }
}

Integer Queues

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

Integer Queues: CUSTOM Functionality

Say we want to vary how put behaves:

trait Doubling extends IntQueue {
  abstract override def put(x: Int) { super.put(2 * x) }
}

Integer Queues: CUSTOM Functionality

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)

Integer Queues: CUSTOM Functionality

Say we want to vary how put behaves:

trait Doubling extends IntQueue {
  abstract override def put(x: Int) { super.put(2 * x) }
}

CLICKER: What's the result?

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

Integer Queues: CUSTOM Functionality

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

Traits (continued), Mar 06

Announcements

No Class on Thurs, Mar 13

Last class on Tues, Mar 11

Let's Pick Up Where We Left Off...

Integer Queues

BASE Specification

trait IntQueue { def get(): Int; def put(x: Int) } 

CORE Functionality

class BasicIntQueue extends IntQueue {
  private val buf =
    new scala.collection.mutable.ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) { buf += x }
}

Integer Queues: CUSTOM Functionality

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) } }

CLICKER: What's the result?

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

Stacking Traits

What happens when mixing multiple traits?

scala> val q = new BasicIntQueue
               with Incrementing with Filtering

Stacking Traits

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 ...

CLICKER: What's the result?

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

Recap

(Single) Inheritance Enables Reuse BUT:

Traits are a powerful take on multiple inheritance.

Additional Resources

Plenty of online reading if you want to learn more:

Several of the examples today adapted from above.