memostack
article thumbnail
Published 2020. 2. 23. 16:21
Scala 추상 타입 (type T) Language/Scala
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.github.io/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
반응형

추상 타입

추상 타입을 이용하면 선언 시점에서 어떤 타입인지 알려지지 않은 타입을 참조할 수 있다.

 

추상타입을 사용하지 않는 아래 예제를 보면

class Food
abstract class Animal {
  def eat(food: Food)
}

class Grass extends Food
class Cow extends Animal {
  // override def eat(food: Grass) {} // 파라미터 타입이 다르기 때문에 오버라이드 할 수 없다.
  override def eat(food: Food) {}
}
  • 동물(Animal)은 음식(Food)을 먹는다.
  • 소(Cow)는 풀(Grass)을 먹는다. (소는 동물을 상속받고, 풀은 음식을 상속받는다)

하지만, 파라미터가 다르기 때문에 Animal을 상속 받는 Cowdef eat(food: Grass) 로 오버라이드(Override) 할 수 없다. (단지, 메소드 명이 같고 파라미터가 다른 오버로드딩(Overloading) 관계일 뿐)

 

추상 타입을 이용하면 위 코드를 모델링 할 수 있다.

class Food
abstract class Animal {
  type SuitableFood <: Food // <: Food는 Food 를 상속받는 서브 클래스들을 의미함
  def eat(food: SuitableFood) // Food 를 상속받는 서브 클래스들을 파라미터로 가짐
}

class Grass extends Food

class Cow extends Animal {
  override type SuitableFood = Grass // Food 의 서브클래스라는 것을 알려줌
  override def eat(food: Grass) {}
}
  • type 키워드와 <: Food 를 이용해서, Food를 상속받는 서브클래스 타입이라는 것을 명시해준다.
  • GrassFood 의 서브클래스이다.
  • Cow 에서 type 을 이용해서 GrassFood 의 서브클래스라는 것을 명시한다.
  • eat 메소드를 오버라이드(override) 할 수 있다.

 

만약, Food 를 상속받는 GrassFish 가 있을 때, Cow 에게 Fish 를 먹이면 컴파일 오류가 발생한다. 반대로 Grass 를 먹일때는 오류가 발생하지 않는다. (의도한대로 동작을 한다)

val cow = new Cow()
cow.eat(new Grass)

class Fish extends Food
cow.eat(new Fish) // 컴파일 에러

Grass 와 Fish는 서로 관계가 없다.

Grass 와 Fish 모두 Food를 상속 받는 서브클래스이지만, Grass와 Fish는 아무런 관계가 없기 때문에 Cow 클래스의 eat 메소드에 파라미터 타입인 Grass가 아닌, Fish 타입을 넣었기 때문에 오류가 발생한다.

 

경로 의존 타입

곰(bear) 클래스를 생성해보자.

class Grass extends Food
class Fish extends Food

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

// Bear 클래스 생성
class Bear extends Animal {
  override type SuitableFood = Fish
  override def eat(food: Fish) {}
}

CowBear 는 동일한 SuitableFood 타입을 갖는다.

 

그렇다면, Bear 에게 CowSuitableFood를 먹이면 정상 동작을 할까? 답은 No

val cow = new Cow
cow.eat(new Grass)

val bear = new Bear
bear.eat(new cow.SuitableFood) // 컴파일 오류 발생
bear.eat(new bear.SuitableFood)

컴파일 오류 발생.

타입의 이름이 SuitableFood로 같지만, 경로 의존 타입(path-dependent type)이 다르다. (쉽게 말해서, 타입이 다르다는 뜻)

 

cow.SuitableFood != bear.SuitableFood

  • 소의 SuitableFood 타입은 cow.SuitableFood 이다.
  • 곰의 SuitableFood 타입은 bear.SuitableFood 이다.

자바와의 차이점

class Outer {
  class Inner
}

자바에서는 위 내부 클래스(Inner)를 표현 할 때, Outer.Inner 라고 표현한다.

스칼라에서는 내부 클래스를 표현할 때, Outer#Inner 로 표현하다.

 

자바와 마찬가지로 스칼라에서도 내부 클래스 인스턴스에는 그 인스턴스를 둘러싸고 있는 외부 인스턴스(Outer)를 가리키는 참조값을 가지고 있다.

따라서, 외부 인스턴스 참조값이 있어야 내부 클래스에 접근 할 수 있다. (반대로 말하면, 외부 인스턴스를 지정하지 않으면, 내부 클래스에 접근 할 수 없다)

val o1 = new Outer
val o1_i = new o1.Inner
val o1_i2 = new Outer#Inner // 오류. 외부 인스턴스 없이 Inner 클래스에 접근할 수 없다.

Outer 의 인스턴스를 먼저 생성해야한다.

세분화한 타입

풀을 먹는 동물들이 포함된 목초지(Pasture) 클래스를 만들어 보자.

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

class Pasture { // 목초지
  var animals: List[AnimalThatEatsGrass] = Nil
}

 

위에서 Cow 라는 풀을 먹는 동물을 만들었다.

하지만, 소 뿐만 아니라 다른 동물을 포함하기 위해, 풀을 먹는 동물(AnimalThatEatsGrass)을 만들어서 또 다시 선언하는 것은 장황하다.

 

세분화한 타입을 사용하면, 위 문제를 해결할 수 있다.

class Pasture2 {
  var animals: List[Animal{type SuitableFood = Grass}] = Nil
}

따로 클래스를 만들 필요없이 중괄호로 멤버 목록을 덧붙이면 된다.

반응형

'Language > Scala' 카테고리의 다른 글

Scala의 열거형(Enumeration type)  (0) 2020.02.23
Scala 추상 val 초기화  (0) 2020.02.23
Scala 추상 멤버 (타입멤버, 메소드, val, var)  (0) 2020.02.22
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.github.io/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
profile

memostack

@bluemiv_mm

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!