Tour of Scala

추상 타입

Language

스칼라에선 값(생성자 파라미터)과 타입(클래스가 제네릭일 경우)으로 클래스가 매개변수화된다. 규칙성을 지키기 위해, 값이 객체 멤버가 될 수 있을 뿐만 아니라 값의 타입 역시 객체의 멤버가 된다. 또한 이런 두 형태의 멤버 모두 다 구체화되거나 추상화될 수 있다.

이 예제에선 클래스 Buffer의 멤버로써 완전히 확정되지 않은 값과 추상 타입을 정의하고 있다.

trait Buffer {
  type T
  val element: T
}

추상 타입은 본성이 완전히 식별되지 않은 타입이다. 위의 예제에선 클래스 Buffer의 각 객체가 T라는 타입 멤버를 갖고 있다는 점만 알 수 있으며, 클래스 Buffer의 정의는 멤버 타입 T에 해당하는 특정 타입이 무엇이지 밝히고 있지 않다. 값 정의와 같이 타입 정의도 서브클래스에서 재정의(override) 할 수 있다. 이것은 타입 경계(추상 타입에 해당하는 예시를 나타내는)를 좀더 엄격하게 함으로써 추상 타입에 대해 좀더 많은 정보를 얻을수 있게 해준다.

다음 프로그램에선 T 타입이 새로운 추상 타입 U로 표현된 Seq[U]의 서브타입이어야 함을 나타내서, 버퍼에 시퀀스 만을 저장하는 클래스 SeqBuffer를 만들었다.

abstract class SeqBuffer extends Buffer {
  type U
  type T <: Seq[U]
  def length = element.length
}

추상 타입 멤버를 포함한 트레잇이나 클래스는 종종 익명 클래스 인스턴스화와 함께 사용된다. 이를 알아보기 위해 정수의 리스트를 참조하는 시퀀스 버퍼를 다루는 프로그램을 살펴보자.

abstract class IntSeqBuffer extends SeqBuffer {
  type U = Int
}

def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
  new IntSeqBuffer {
        type T = List[U]
        val element = List(elem1, elem2)
      }
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)

메소드 newIntSeqBuf의 반환 타입은 트레잇 Buffer의 특수화를 따르며, 타입 UInt와 같아진다. 메소드 newIntSeqBuf 내부의 익명 클래스 인스턴스화에서도 비슷한 타입 별칭이 있다. 여기선 T 타입이 List[Int]를 가리키는 IntSeqBuf의 새로운 인스턴스를 생성한다.

추상 타입 멤버를 클래스의 타입 파라미터로 하거나 클래스의 타입 파라미터로 추상 타입 멤버로를사용할 수 있음에 주목하자. 다음은 타입 파라미터만을 사용한, 앞서 살펴본 코드의 새로운 버전이다.

abstract class Buffer[+T] {
  val element: T
}
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
  def length = element.length
}
def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
  new SeqBuffer[Int, List[Int]] {
    val element = List(e1, e2)
  }
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)

여기선 가변성 어노테이션을 사용해야만 한다는 점에 유의하자. 이를 사용하지 않으면 메소드 newIntSeqBuf에서 반환되는 객체의 특정 시퀀스 구현 타입을 감출 수 없게 된다. 뿐만 아니라 추상 타입을 타입 파라미터로 대체할 수 없는 경우도 있다.

윤창석, 이한욱 옮김, 고광현 수정

Contributors to this page: