Code Examples for

Programming in Scala, Second Edition

Return to chapter index

20 Abstract Members

Sample run of chapter's interpreter examples

20.1 A quick tour of abstract members


// In file abstract-members/AbsConc.scala trait Abstract { type T def transform(x: T): T val initial: T var current: T }
// In file abstract-members/AbsConc.scala class Concrete extends Abstract { type T = String def transform(x: String) = x + x val initial = "hi" var current = initial }

20.2 Type members

20.3 Abstract vals


val initial: String
val initial = "hi"
def initial: String
// In file abstract-members/DefOverVal.scala.err abstract class Fruit { val v: String // `v' for value def m: String // `m' for method } abstract class Apple extends Fruit { val v: String val m: String // OK to override a `def' with a `val' } abstract class BadApple extends Fruit { def v: String // ERROR: cannot override a `val' with a `def' def m: String }

20.4 Abstract vars


// In file abstract-members/AbstractTime.scala trait AbstractTime { var hour: Int var minute: Int }
// In file abstract-members/AbstractTime.scala trait AbstractTime { def hour: Int // getter for `hour' def hour_=(x: Int) // setter for `hour' def minute: Int // getter for `minute' def minute_=(x: Int) // setter for `minute' }

20.5 Initializing abstract vals


// In file abstract-members/Misc.scala trait RationalTrait { val numerArg: Int val denomArg: Int }
// In file abstract-members/Misc.scala new RationalTrait { val numerArg = 1 val denomArg = 2 }
new Rational(expr1, expr2)
// In file abstract-members/Misc.scala new RationalTrait { val numerArg = expr1 val denomArg = expr2 }
// In file abstract-members/Misc.scala trait RationalTrait { val numerArg: Int val denomArg: Int require(denomArg != 0) private val g = gcd(numerArg, denomArg) val numer = numerArg / g val denom = denomArg / g private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) override def toString = numer +"/"+ denom }
scala> val x = 2 x: Int = 2 scala> new RationalTrait { | val numerArg = 1 * x | val denomArg = 2 * x | } java.lang.IllegalArgumentException: requirement failed at scala.Predef$.require(Predef.scala:134) at RationalTrait$class.$init$(<console>:8) at $anon$1.<init>(<console>:8) ...
scala> new { | val numerArg = 1 * x | val denomArg = 2 * x | } with RationalTrait res1: java.lang.Object with RationalTrait = 1/2
// In file abstract-members/Misc.scala object twoThirds extends { val numerArg = 2 val denomArg = 3 } with RationalTrait
scala> new { | val numerArg = 1 | val denomArg = this.numerArg * 2 | } with RationalTrait <console>:9: error: value numerArg is not a member of object $iw val denomArg = this.numerArg * 2 ^
// In file abstract-members/Misc.scala class RationalClass(n: Int, d: Int) extends { val numerArg = n val denomArg = d } with RationalTrait { def + (that: RationalClass) = new RationalClass( numer * that.denom + that.numer * denom, denom * that.denom ) }
scala> object Demo { | val x = { println("initializing x"); "done" } | } defined module Demo
scala> Demo initializing x res3: Demo.type = Demo$@17469af scala> Demo.x res4: java.lang.String = done
scala> object Demo { | lazy val x = { println("initializing x"); "done" } | } defined module Demo scala> Demo res5: Demo.type = Demo$@11dda2d scala> Demo.x initializing x res6: java.lang.String = done
trait LazyRationalTrait { val numerArg: Int val denomArg: Int lazy val numer = numerArg / g lazy val denom = denomArg / g override def toString = numer +"/"+ denom private lazy val g = { require(denomArg != 0) gcd(numerArg, denomArg) } private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) }
scala> val x = 2 x: Int = 2 scala> new LazyRationalTrait { | val numerArg = 1 * x | val denomArg = 2 * x | } res7: java.lang.Object with LazyRationalTrait = 1/2

20.6 Abstract types


// In file abstract-members/BuggyAnimals.scala.err class Food abstract class Animal { def eat(food: Food) }
// In file abstract-members/BuggyAnimals.scala.err class Grass extends Food class Cow extends Animal { override def eat(food: Grass) {} // This won't compile }
BuggyAnimals.scala:7: error: class Cow needs to be abstract, since method eat in class Animal of type (Food)Unit is not defined class Cow extends Animal { ^ BuggyAnimals.scala:8: error: method eat overrides nothing override def eat(food: Grass) {} ^
class Food abstract class Animal { def eat(food: Food) } class Grass extends Food class Cow extends Animal { override def eat(food: Grass) {} // This won't compile, } // but if it did,... class Fish extends Food val bessy: Animal = new Cow bessy eat (new Fish) // ...you could feed fish to cows.
// In file abstract-members/Animals.scala class Food abstract class Animal { type SuitableFood <: Food def eat(food: SuitableFood) }
// In file abstract-members/Animals.scala class Grass extends Food class Cow extends Animal { type SuitableFood = Grass override def eat(food: Grass) {} }
scala> class Fish extends Food defined class Fish scala> val bessy: Animal = new Cow bessy: Animal = Cow@2e3919 scala> bessy eat (new Fish) <console>:12: error: type mismatch; found : Fish required: bessy.SuitableFood bessy eat (new Fish) ^

20.7 Path-dependent types


class DogFood extends Food class Dog extends Animal { type SuitableFood = DogFood override def eat(food: DogFood) {} }
scala> val bessy = new Cow bessy: Cow = Cow@e7bbeb scala> val lassie = new Dog lassie: Dog = Dog@ce38f1 scala> lassie eat (new bessy.SuitableFood) <console>:14: error: type mismatch; found : Grass required: DogFood lassie eat (new bessy.SuitableFood) ^
scala> val bootsie = new Dog bootsie: Dog = Dog@66db21 scala> lassie eat (new bootsie.SuitableFood)
// In file abstract-members/Misc.scala class Outer { class Inner }
val o1 = new Outer val o2 = new Outer
scala> new o1.Inner res11: o1.Inner = Outer$Inner@1df6ed6
scala> new Outer#Inner <console>:7: error: Outer is not a legal prefix for a constructor new Outer#Inner ^

20.8 Structural subtyping


Animal { type SuitableFood = Grass }
// In file abstract-members/Pasture.scala class Pasture { var animals: List[Animal { type SuitableFood = Grass }] = Nil // ... }
// In file abstract-members/LoanPattern.scala using(new PrintWriter("date.txt")) { writer => writer.println(new Date) }
// In file abstract-members/LoanPattern.scala using(serverSocket.accept()) { socket => socket.getOutputStream().write("hello, world\n".getBytes) }
def using[T, S](obj: T)(operation: T => S) = { val result = operation(obj) obj.close() // type error! result }
// In file abstract-members/LoanPattern.scala def using[T <: { def close(): Unit }, S](obj: T) (operation: T => S) = { val result = operation(obj) obj.close() result }

20.9 Enumerations


// In file abstract-members/Misc.scala object Color extends Enumeration { val Red = Value val Green = Value val Blue = Value }
// In file abstract-members/Misc.scala object Color extends Enumeration { val Red, Green, Blue = Value }
// In file abstract-members/Misc.scala import Color._
// In file abstract-members/Misc.scala object Direction extends Enumeration { val North, East, South, West = Value }
object Direction extends Enumeration { val North = Value("North") val East = Value("East") val South = Value("South") val West = Value("West") }
scala> for (d <- Direction.values) print(d +" ") North East South West
scala> Direction.East.id res14: Int = 1
scala> Direction(1) res15: Direction.Value = East

20.10 Case study: Currencies


// In file abstract-members/Misc.scala // A first (faulty) design of the Currency class abstract class Currency { val amount: Long def designation: String override def toString = amount +" "+ designation def + (that: Currency): Currency = ... def * (x: Double): Currency = ... }
79 USD 11000 Yen 99 Euro
// In file abstract-members/Misc.scala new Currency { val amount = 79L def designation = "USD" }
// In file abstract-members/Misc.scala abstract class Dollar extends Currency { def designation = "USD" } abstract class Euro extends Currency { def designation = "Euro" }
// In file abstract-members/AbstractCurrency.scala.err // A second (still imperfect) design of the Currency class abstract class AbstractCurrency { type Currency <: AbstractCurrency val amount: Long def designation: String override def toString = amount +" "+ designation def + (that: Currency): Currency = ... def * (x: Double): Currency = ... }
abstract class Dollar extends AbstractCurrency { type Currency = Dollar def designation = "USD" }
// In file abstract-members/AbstractCurrency.scala.err def + (that: Currency): Currency = new Currency { val amount = this.amount + that.amount }
error: class type required def + (that: Currency): Currency = new Currency { ^
abstract class AbstractCurrency { type Currency <: AbstractCurrency // abstract type def make(amount: Long): Currency // factory method ... // rest of class }
myDollar.make(100) // here are a hundred more!
// In file abstract-members/Misc.scala abstract class CurrencyZone { type Currency <: AbstractCurrency def make(x: Long): Currency abstract class AbstractCurrency { val amount: Long def designation: String override def toString = amount +" "+ designation def + (that: Currency): Currency = make(this.amount + that.amount) def * (x: Double): Currency = make((this.amount * x).toLong) } }
// In file abstract-members/Misc.scala object US extends CurrencyZone { abstract class Dollar extends AbstractCurrency { def designation = "USD" } type Currency = Dollar def make(x: Long) = new Dollar { val amount = x } }
class CurrencyZone { ... val CurrencyUnit: Currency }
// In file abstract-members/CurrencyZone.scala object US extends CurrencyZone { abstract class Dollar extends AbstractCurrency { def designation = "USD" } type Currency = Dollar def make(cents: Long) = new Dollar { val amount = cents } val Cent = make(1) val Dollar = make(100) val CurrencyUnit = Dollar }
// In file abstract-members/CurrencyZone.scala override def toString = ((amount.toDouble / CurrencyUnit.amount.toDouble) formatted ("%."+ decimals(CurrencyUnit.amount) +"f") +" "+ designation)
// In file abstract-members/CurrencyZone.scala private def decimals(n: Long): Int = if (n == 1) 0 else 1 + decimals(n / 10)
// In file abstract-members/CurrencyZone.scala object Europe extends CurrencyZone { abstract class Euro extends AbstractCurrency { def designation = "EUR" } type Currency = Euro def make(cents: Long) = new Euro { val amount = cents } val Cent = make(1) val Euro = make(100) val CurrencyUnit = Euro } object Japan extends CurrencyZone { abstract class Yen extends AbstractCurrency { def designation = "JPY" } type Currency = Yen def make(yen: Long) = new Yen { val amount = yen } val Yen = make(1) val CurrencyUnit = Yen }
// In file abstract-members/Misc.scala object Converter { var exchangeRate = Map( "USD" -> Map("USD" -> 1.0 , "EUR" -> 0.7596, "JPY" -> 1.211 , "CHF" -> 1.223), "EUR" -> Map("USD" -> 1.316 , "EUR" -> 1.0 , "JPY" -> 1.594 , "CHF" -> 1.623), "JPY" -> Map("USD" -> 0.8257, "EUR" -> 0.6272, "JPY" -> 1.0 , "CHF" -> 1.018), "CHF" -> Map("USD" -> 0.8108, "EUR" -> 0.6160, "JPY" -> 0.982 , "CHF" -> 1.0 ) ) }
// In file abstract-members/CurrencyZone.scala def from(other: CurrencyZone#AbstractCurrency): Currency = make(math.round( other.amount.toDouble * Converter.exchangeRate (other.designation)(this.designation)))
// In file abstract-members/Misc.scala abstract class CurrencyZone { type Currency <: AbstractCurrency def make(x: Long): Currency abstract class AbstractCurrency { val amount: Long def designation: String def + (that: Currency): Currency = make(this.amount + that.amount) def * (x: Double): Currency = make((this.amount * x).toLong) def - (that: Currency): Currency = make(this.amount - that.amount) def / (that: Double) = make((this.amount / that).toLong) def / (that: Currency) = this.amount.toDouble / that.amount def from(other: CurrencyZone#AbstractCurrency): Currency = make(math.round( other.amount.toDouble * Converter.exchangeRate (other.designation)(this.designation))) private def decimals(n: Long): Int = if (n == 1) 0 else 1 + decimals(n / 10) override def toString = ((amount.toDouble / CurrencyUnit.amount.toDouble) formatted ("%."+ decimals(CurrencyUnit.amount) +"f") +" "+ designation) } val CurrencyUnit: Currency }
scala> import org.stairwaybook.currencies._
scala> Japan.Yen from US.Dollar * 100 res16: Japan.Currency = 12110 JPY scala> Europe.Euro from res16 res17: Europe.Currency = 75.95 EUR scala> US.Dollar from res17 res18: US.Currency = 99.95 USD
scala> US.Dollar * 100 + res18 res19: US.Currency = 199.95 USD
scala> US.Dollar + Europe.Euro <console>:10: error: type mismatch; found : Europe.Euro required: US.Currency US.Dollar + Europe.Euro ^

20.11 Abstract versus generic

20.12 Conclusion

For more information about Programming in Scala, Second Edition (the "Stairway Book"), please visit:

http://www.artima.com/shop/programming_in_scala_2ed

and:

http://booksites.artima.com/programming_in_scala_2ed

Copyright © 2007-2010 Artima, Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.