Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행했다.

20장

  • 추상 멤버 : 클래스/트레이트 안에서 완전한 정의를 갖고 있지 않은 멤버
  • 메소드, 필드, 타입
// 추상 멤버 예
trait Abstract {
  type T
  def transform(x: T): T
  val initial: T
  var current: T
}

추상 타입

  • 실제 이름이 너무 길거나 의미가 불명확할 때
  • 서브 클래스에서 꼭 정의해야하는 추상 타입 선언

추상 val

  • 클래스 안에서 변수에 대해 값은 알 수 없지만, 인스턴스에서 변하지 않는 경우
// 추상 val 선언
val initial: String

// 구현
val initial = "hi"
  • 추상 메소드는 val 정의로 구현할 수도 있음

추상 var

trait AbstractTime {
  var hour: Int
  var minute: Int
}

// 위와 동일한 실제 확장 모습
trait AbstractTime {
  def hour: Int
  def hour_=(x: Int)
  def minute: Int
  def minute_=(x: Int)
}

추상 val 초기화

trait RationalTrait {
  val numerArg: Int
  val denomArg: Int
}

// 추상 val 정의 구현
// 익명 클래스 인스턴스 생성
new RationalTrait {
  val numerArg = 1
  val denomArg = 2
}

필드 미리 초기화

  • 필드 정의를 중괄호에 넣어서 슈퍼클래스 생성자 호출 앞에 위치
// 익명 클래스
new {
  val numerArg = 1 * x
  val denomArg = 2 * x
} with RationalTrait

// 정의 중인 객체/클래스의 extends 키워드 다음에 정의
object twoThirds extends {
  val numerArg = 2
  val denomArg = 3
} with RationalTrait
  • 초기화시 생성 중인 객체를 언급할 수 없음

지연 계산

object Demo {
  val x = { println("initializing x"); "done" }
}

// 지연 계산
object Demo {
  lazy val x = { println("initializing x"); "done" }
}

// Demo
// Demo.x
// 미리 초기화할 필요 없는 RationalTrait
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)
}

추상 타입 예

// 잘못된 예
class Food
abstract class Animal {
  def eat(food: Food)
}

class Grass extends Food
class Cow extends Animal {
  override def eat(food: Grass) {}  // 컴파일 안됨
}
// 올바른 예
class Food
abstract class Animal {
  type SuitableFood <: Food
  def eat(food: SuitableFood)
}

class Grass extends Food
class Cow extends Animal {
  type SuitableFood = Grass
  override def eat(food: SuitableFood) {}
}

경로에 의존하는 타입

  • 외부 객체에 이름을 붙임
  • 실제 타입이 같으면 동일한 타입
class DogFood extends Food
class Dog extends Animal {
  type SuitableFood = DogFood
  override def eat(food: DogFood) {}
}

val bessy = new Cow
val lassie = new Dog
lassie eat (new bessy.SuitableFood) // error
val bootsie = new Dog
lassie eat (new bootsie.SuitableFood)   // ok

구조적 서브타이핑

  • 이름에 의한 서브타입 : 클래서 A가 클래스 B를 상속하는 경우.
  • 구조적 서브타입 : 두 타입의 멤버가 같음
// 세분화한 타입 사용 예
Animal { type SuitableFood = Grass }

class Pasture {
  var animals: List[Animal { type SuitableFood = Grass }] = Nil
  // ...
}

// 여러 클래스를 그룹으로 다루는 예
def using[T <: { def close(): Unit }, S](obj: T)(operation: T => S) = {
  val result = operation(obj)
  obj.close()
  result
}

열거형

  • scala.Enumeration 클래스
  • 각 값의 타입은 Value
object Color extends Enumeration {
  val Red = Value
  val Green = Value
  val Blue = Value
}

// 위와 동일한 코드
object Color extends Enumeration {
  val Red, Green, Blue = Value
}

// Color.Value != Direction.Value
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")
}

for (d <- Direction.values) print(d + " ")
// North East South West

Direction.East.id
// 1

Direction(1)
// East

예 - 통화 변환

  • 첫번째 구현
abstract class Currency {
  val amount: Long
  def designation: String
  override def toString = amount +" "+ designation
  def + (that: Currency): Currency = ...
  def * (x: Double): Currency = ...
}

abstract class Dollar extends Currency {
  def designation = "USD"
}
  • 두번째 구현
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"
}
  • 최종 구현
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 * (that: Double): Currency = make((this.amount * x).toLong)
    def - (that: Currency): Currency = make(this.amount - that.amount)
    def / (that: Double): Currency = make((this.amount / that).toLong)
    def / (that: Currency): 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 < 10) 0 else 1 + decimals(n / 10)
    override def toString =
      ((amount.toDouble / CurrencyUnit.amount.toDouble) formatted ("%."+ decimals(CurrencyUnit.amount) +"f") +" "+ designation)
  }
  val CurrencyUnit: Currency
}

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

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
}

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 = "USD"
  }
  type Currency = Yen
  def make(cents: Long) = new Yen {
    val amount = cents
  }
  val Yen = make(1)
  val CurrencyUnit = Yen
}

// 환전
Japan.Yen from US.Dollar * 100


+ Recent posts