Scala CH20. 추상 멤버
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.
추상 멤버 : 클래스나 트레이트의 멤버가 그 클래스 안에 완전한 정의를 갖고 있지 않으면 추상 클래스
추상 멤버는 그 멤버가 정의된 클래스를 상속한 서브클래스에서 구현해야함.
자바에서는 추상 메소드를 정의
총 4가지 종류의 추상 멤버,ㅣ val, var, 메소드, 타입
미리 초기화된 필드 : pre-initialized field
지연 : lazy val
경로 의존적인 타입 : path dependent type
열거형 : enumeration
1. 간단한 추상 멤버
추상타입 T, 추상 메소드, 추상 val 추상 var
trait Abstract { type T def transform(x: T) T val initial: T var current: T }
class Concrete extends Abstract { type T = String def transform(x: String) = x + x val initial = "Hi" var current = initial } |
2. 타입멤버
추상 타입은 클래스나 트레이트의 멤버로 정의없이 선언된 타입
트레이트는 정의상 추상적이지만, 스칼라에서 추상타입이라고 부르지 않음.
스칼라의 추상타입은 Abstract 트레이트의 T 타입 처럼 항상 어떤 클래스나 트레이트의 멤버이다
alias 로 생각할 수도 있음
3. 추상 val
val initial: String
이름과 타입은 추지만 값은 지정하지 않음.
변수에 정확한 값은 모르지만, 그 변수가 클래스의 인스턴스에서 변하지 않으리란 사실은 알고 있을 때 추상 val을 사용한다.
initial의 추상 val이면 클라이언트는 obj.initial을 사용할 때 마다 같은 값을 얻을 수 있음을 확신
intial이 추상 메소드라면, 그런 보장은 없음.
추상 val은 그에 대한 구현에 제약을 가한다. 구현시 val 정의를 사용해야한다는 제약이다.
4. 추상 var
trait AbstractTime { var hour: Int var minute: Int }
getter/setter 스칼라가 만들어줌. |
5. 추상 val 초기화
추상 val은 때때로 슈퍼 클래스 파라미터와 같은 역할을 한다.
슈퍼 클래스에 빠진 미세한 부분을 서브 클래스에 전달 할 수 있는 수단
트레이트에서는 파라미터를 넘길 생성자가 없기 때문이다
트레이트를 파라미터화 할려면 서브 클래스에서 구현하는 추상 val을 통해서 수행
trait RationalTrait { val numerArg: Int // 유리수 분자 var denomArg: Int // 분모 }
new RationalTrait( val numerArg = 1 val denomArg = 2 } // 익명클래스
new RationalTrait { val numerArg = expr1 val denomArg = expr2 } // 익명 클래스 초기화 중에 표현식 expr1, expr2 계산 // 익명 클래스는 RationalTrait 다음에 초기화 됨 numberArg와 demonArg값은 RationalTrait 초기화 전에는 사용가능하지 않기 때문에 0을 돌려 받음.
// 아래의 버전에서는 문제가 됨 trait RationalTrait { val numerArg: Int var denumArg: Int require(denomArg != 0) private val g = gcd(numerArg, denomArg) val numer = numerArg / g var denom = denumArg / g private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) override def toString = numer + "/" + denum }
val x = 2 new RationalTrait( val numerArg = 1 * x var denumArg = 2 * x )
requirement failed Exception
RationalTrait 클래스 초기화 할때 denumArg 값이 0이기 때문 |
클래스의 파라미터 인자는 클래스 생성자에 전달되기 전에 계산한다
반면 서브클래스의 val 정의를 계산하는 것은 슈퍼 클래스를 초기화 한 다음에 이뤄진다.
이에 대한 해결로 필드를 미리 초기화 하는 것과 지연 val 하는 방법 두 가지가 있음. (방법이 미리 다 해 놓거나 아님 다 초기화 되고 하거나...)
필드를 미리 초기화
중괄호를 이용하여 슈퍼클래스 호출 전에 서브클래스 필드 초기화
미리 초기화 한 필드는 클래스 생성자의 인자와 비슷하게 동작함.
new { val numberArg = 1 * x val denumArg = 2 * x } with RationalTrait
object twoThird extends { val numerArg = 2 var denumArg = 3 } with RationalTrait
class RationalClass(n: Int, d: Int) extends { val numerArg = n val denomArg = d } with RationalTrait { def + (that: RationalClass) = new RationalClass( numer * that.denom + that.number * denom, denom * that.denom ) } |
지연 계산 val 변수
내가 초기화 하는게 아닌 system이 초기화하게 지연,
어떤 val 정의를 lazy 하게 만들면 됨.
object Demo { lazy val x = { println("initilized"); "done"} }
Demo.x 선택시 println 부분 호출됨.
trait LazyRationalTrait { val numerArg: Int val denomArg: Int lazy val numer = numberArg: g lazy val denom = denomArg: g override def toString = numer + "/" + denum private lazy g = ( require(denomArg != 0) gcd(numerArg, denomArg) ) private def gcd(a: Int, a: Int): Int = if (b == 0) a else gcd(b, a % b) } |
6. 추상 타입
추상 타입 선언은 서브 클래스에서 구체적으로 정해야 하는 어떤 대상에 대한 빈공간을 마련해 두는 것
T는 선언 시점에는 어떤 타입인지 알려져 있지 않은 타입을 참조하기 위해 사용함
class Food abstract class Animal { type SuitableFood <: Food // SuitableFood의 상위 바운스는 Food def eat(food: SuitableFood) }
class Grass extends Food class Cow extends Animal { type SuitableFood = Grass override def eat(food: Grass) = {} }
class Fish extends Food var bessy: Animal = new Cow bessy eat (new Fish)
error: type mismatch required: bessy.SuitableFood
//의미 : messy가 참조하는 객체의 멤버인 SuitableType |
7. 경로에 의존하는 타입
마지막 오류 메세지는 eat 메소드가 요구하는 타입인 bessy.SuitableFood
경로에 의존하는 타입 path-dependent type
경론ㄴ 객체에 대한 참조를 의미, 참조변수 : 싱글톤 객체 이름
문제는 eat 메소드에 넘기는 SuitableFood 객체의 타입인 bessy.SuitableFood는 eat 의 파라메터 타입인 lassie.suitableFood와는 같지 않음.
하지만 두 Dog의 경우는 실제로 동일한 타입임.
경로 의존 타입은 자바의 내부 클래스 타입과 문법이 비슷하지만,
경로 의존 타입은 외부 객체에 이름을 붙이는 반면
내부 클래스 타입 이름의 외부 클래스에 이름을 붙인다는 점
자바와 마찬가지로 스칼라에서도 내부 클래스 인스턴스에는 그 인스턴스를 둘러 싼 클래스와 종류
외부 클래스 인스턴스를 지정하지 않고 내부 클래스만 인스턴트.
다른 방법
o1.Inner 타입은 특정 이후 후 인스턴스를 가져오기
경로 의존 타입은 자바의 클래스 타입과 비슷하지만,
new o1.Inner |
8. 세분화한 타입
어떤 클래스 A가 다른 클래스 B를 상속할 때, 전자 (A)가 후자(B)dml 이름에 의한 서브타입 nominal subtype
구조적인 서브 타이핑 지원 : structual subtyping
세분화한 타입을 사용하면 됨 : refinement type
Animal {type SuitableFood = Grass}
// 목초리 class Pasture { var animals: List[Animal {type SuitableFood = Grass}] = Nil } |
9. 열거혐
경로 의존적 타입의 재미있는 활용 ; 스칼라의 열거형 : enumeration type
object Color extends Enumeration { val Red = Value val Grren = Value val Blue = Value }
object Direction extends Enumeration { val North = Value("North") val East = Value("East") val South = Value("South") val West = Value("West") }
Direction.Value 과 Color.Value는 다름 |
10. 사례 연구
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) ) }
abstract class CurrentyZone { type Currency <: AbstractCurrency def make(x: Long): Currency
abstract class AbstractCurrency { val amount: Long def designation: String
def + (this: Currency): Currency = make(this.amount + that.amount)
def * (x: Double): Currency = make(this.amount * x).toLong) def - (this: Currency): Currency = make(this.amount - that.amount) def / (this: Double) = make((this.amount / that).toLong) def / (this: Currency): Currency = make(this.amount / that.amount)
def from(other: CurrencyZone#AbstractCurrency): Currency = make(math.round( other.amount.toDouble * Converter.exchangeRate (other.designation)(this.designation) private def decimal(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) }
var CurrencyUnit: Currency }
Japan.Yen from US.Dollar * 100
Europe.Euro from res16
|