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
'Developing' 카테고리의 다른 글
Programming in Scala 스터디 정리 - 21장. 암시적 변환과 암시적 파라미터 (0) | 2017.10.11 |
---|---|
Programming in Scala 스터디 정리 - 19장. 타입 파라미터화 (0) | 2017.10.01 |
Programming in Scala 스터디 정리 - 18장. 상태가 있는 객체 (0) | 2017.09.30 |