Code Examples for

Programming in Scala, Fifth 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): Unit // setter for `hour' def minute: Int // getter for `minute' def minute_=(x: Int): Unit // 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 = s"numer/denom"
scala> val x = 2 val 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:280) at RationalTrait.$init$(<console>:4) ... 28 elided
// 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 = s"numer/denom"
scala> new RationalTrait(1 * x, 2 * x) {} val res1: RationalTrait = 1/2
// In file abstract-members/Misc.scala object TwoThirds extends RationalTrait(2, 3)
// In file abstract-members/Misc.scala class RationalClass(n: Int, d: Int) extends RationalTrait(n, d): def + (that: RationalClass) = new RationalClass( numer * that.denom + that.numer * denom, denom * that.denom )
object Demo: val x = { println("initializing x"); "done" }
scala> Demo initializing x val res0: Demo.type = Demo$@3002e397 scala> Demo.x val res1: String = done
object Demo: lazy val x = { println("initializing x"); "done" } scala> Demo val res2: Demo.type = Demo$@24e5389c scala> Demo.x initializing x val res3: String = done
trait LazyRationalTrait: val numerArg: Int val denomArg: Int lazy val numer = numerArg / g lazy val denom = denomArg / g override def toString = s"numer/denom" private lazy val g = require(denomArg != 0) gcd(numerArg, denomArg) private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b)
scala> val x = 2 val x: Int = 2 scala> new LazyRationalTrait: | val numerArg = 1 * x | val denomArg = 2 * x | val res4: LazyRationalTrait = 1/2

20.6 Abstract types


// In file abstract-members/BuggyAnimals.scala.err class Food abstract class Animal: def eat(food: Food): Unit
// In file abstract-members/BuggyAnimals.scala.err class Grass extends Food class Cow extends Animal: override def eat(food: Grass) = {} // This won't compile
2 | class Cow extends Animal: | ^ |class Cow needs to be abstract, since |def eat(food: Food): Unit is not defined (Note that Food |does not match Grass: class Grass is a subclass of class |Food, but method parameter types must match exactly.) 3 | override def eat(food: Grass) = {} // This won't... | ^ | method eat has a different signature than the | overridden declaration
class Food abstract class Animal: def eat(food: Food): Unit 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): Unit
// In file abstract-members/Animals.scala class Grass extends Food class Cow extends Animal: type SuitableFood = Grass override def eat(food: Grass) = {}
class Fish extends Food val bessy: Animal = new Cow scala> bessy.eat(new Fish) 1 |bessy.eat(new Fish) | ^^^^^^^^ | Found: Fish | Required: bessy.SuitableFood

20.7 Path-dependent types


class DogFood extends Food class Dog extends Animal: type SuitableFood = DogFood override def eat(food: DogFood) = {}
val bessy = new Cow val lassie = new Dog scala> lassie.eat(new bessy.SuitableFood) 1 |lassie.eat(new bessy.SuitableFood) | ^^^^^^^^^^^^^^^^^^^^^^ | Found: Grass | Required: DogFood
val bootsie = new Dog lassie.eat(new bootsie.SuitableFood)
// In file abstract-members/Misc.scala class Outer: class Inner
val o1 = new Outer val o2 = new Outer
new o1.Inner
scala> new Outer#Inner 1 |new Outer#Inner | ^^^^^^^^^^^ | Outer is not a valid class prefix, since it is | not an immutable path

20.8 Refinement types


Animal { type SuitableFood = Grass }
// In file abstract-members/Animals.scala class Pasture: var animals: List[Animal { type SuitableFood = Grass }] = Nil // ...

20.9 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 = s"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 = s"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
7 | new Currency: | ^^^^^^^^ | AbstractCurrency.this.Currency is not a class type 8 | val amount = this.amount + that.amount | ^ | Recursive value amount needs type
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 = s"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 }
abstract class CurrencyZone: ... val CurrencyUnit: Currency
// In file abstract-members/CurrencyZone.scala override def toString = ((amount.toDouble / CurrencyUnit.amount.toDouble) .formatted(s"%.${decimals(CurrencyUnit.amount)}f") + " " + designation)
// 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 private def decimals(n: Long): Int = if n == 1 then 0 else 1 + decimals(n / 10)
// 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/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.8498, "JPY" -> 1.047, "CHF" -> 0.9149), "EUR" -> Map("USD" -> 1.177, "EUR" -> 1.0, "JPY" -> 1.232, "CHF" -> 1.0765), "JPY" -> Map("USD" -> 0.9554, "EUR" -> 0.8121, "JPY" -> 1.0, "CHF" -> 0.8742), "CHF" -> Map("USD" -> 1.093, "EUR" -> 0.9289, "JPY" -> 1.144, "CHF" -> 1.0) )
// 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(s"%.${decimals(CurrencyUnit.amount)}f") + " " + designation) end AbstractCurrency val CurrencyUnit: Currency end CurrencyZone
scala> val yen = Japan.Yen.from(US.Dollar * 100) val yen: Japan.Currency = 10470 JPY scala> val euros = Europe.Euro.from(yen) val euros: Europe.Currency = 85.03 EUR scala> val dollars = US.Dollar.from(euros) val dollars: US.Currency = 100.08 USD
scala> US.Dollar * 100 + dollars res3: US.Currency = 200.08 USD
scala> US.Dollar + Europe.Euro 1 |US.Dollar + Europe.Euro | ^^^^^^^^^^^ |Found: (Europe.Euro : Europe.Currency) |Required: US.Currency(2) |where: Currency is a type in object Europe which | is an alias of Europe.Euro | Currency(2) is a type in object US which is | an alias of US.Dollar

20.10 Abstract versus generic

20.11 Conclusion

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

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

and:

http://booksites.artima.com/programming_in_scala_5ed

Copyright © 2007-2020 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.