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

19장

타입 파라미터화(type parameterization)

  • 제너릭 사용

함수형 큐

  • head
  • tail
  • enqueue
// enqueue 연산이 n 시간 걸림
class SlowAppendQueue[T] (elems: List[T]) {
  def head = elems.head
  def tail = new SlowAppendQueue(elems.tail)
  def enqueue(x: T) = new SlowAppendQueue(elems ::: List(x))
}
// head, tail 연산이 n 시간 걸림
class SlowHeadQueue[T] (smele: List[T]) {
  def head = smele.last
  def tail = new SlowHeadQueue(smele.init)
  def enqueue(x: T) = new SlowHeadQueue(x :: smele)
}
// head, tail, enqueue를 비슷한 빈도로 호출하는 경우, 상수 복잡도
class Queue[T] {
  private val leading: List[T]
  private val trailing: List[T]
} {
  private def mirror =
    if (leading.isEmpty) new Queue(trailing.reverse, Nil)
    else this
  def head = mirror.leading.head
  def tail = {
    val q = mirror
    new Queue(q.leading.tail, q.trailing)
  }
  def enqueue(x: T) = new Queue(leading, x :: trailing)
}

정보 은닉

비공개 생성자

  • 파라미터 목록 바로 앞에 private 수식자를 붙임
class Queue[T] private (
  private val leading: List[T],
  private val trailing: List[T]
)
  • 보조 생성자 생성
def this() = this(Nil, Nil)
def this(elems: T*) = this(elems.toList, Nil)   // T*는 파라미터를 반복 (8.8절)
  • 팩토리 메소드
object Queue {
  def apply[T](xs: T*) = new Queue[T](xs.toList, Nil)
}

비공개 클래스

trait Queue[T] {
  def head: T
  def tail: Queue[T]
  def enqueue(x: T): Queue[T]
}

object Queue {
  def apply[T](xs: T*): Queue[T] =
    new QueueImpl[T](xs.toList, Nil)
  
  private class QueueImpl[T] (
    private val leading: List[T],
    private val trailing: List[T]
  ) extends Queue[T] {
    def mirror =
      if (leading.isEmpty) new QueueImpl(trailing.reverse, Nil)
      else this
    def head: T = mirror.leading.head
    def tail: QueueImpl[T] = {
      val q = mirror
      new QueueImpl(q.leading.tail, q.trailing)
    }
    def enqueue(x: T) = new QueueImpl(leading, x :: trailing)
  }
}

변성(variance) 표기

  • 타입 생성자 : 타입 파라미터를 지정하면 타입을 만들 수 있음. (ex. Queue -> Queue[Int], Queue[String])
  • 제네릭 클래스/트레이트 : 여러 구체적인 타입 정의할 수 있음. (ex. 제네릭 큐 -> 구체적 큐)
  • 제네릭 타입은 기본적으로 무공변(nonvariant)

공변 서브타입 관계

  • 파라미터 앞에 +
  • T가 S의 서브타입 -> Queue[T]가 Queue[S]의 서브타입
trait Queue[+T] { ... }

반공변 서브타입 관계

  • 파라미터 앞에 -
  • T가 S의 서브타입 -> Queue[S]가 Queue[T]의 서브타입
trait Queue[-T] { ... }

변성과 배열

  • 배열은 무공변(자바는 공변)
  • 명시적인 변환이 필요함
val a1 = Array("abc")   // Array[String]
val a2: Array[Object] = a1.asInstanceOf[Array[Object]]

변성 표기 검사

  • 변경 가능 필드(세터 메소드)
  • 제네릭 타입의 파라미터가 메소드 파라미터의 타입인 경우
  • 본문의 모든 위치를 긍정적, 부정적, 중립적으로 구분하여 검사
  • + 표기는 긍정적 위치, - 표기는 부정적 위치에만 가능하며, 변성 표기 없는 것은 어디나 가능

위치 구분

  • 선언 중인 클래스의 최고 수준의 위치 = 긍정적
  • 하위 내포 수준은 그 수준을 둘러싼 상위 수준과 같음
  • 메소드 값 파라미터 위치는 메소드 바깥의 위치의 구분을 뒤집는다.
  • 메소드 타입 파라미터 위치는 구분을 뒤집는다.
  • - 표기의 경우 현재 구분을 뒤집는다.
abstract class Cat[-T, +U] {
  def meow[W](volume: T, listener: Cat[U, T]): Cat[Cat[U,T], U]
}
// meow 의 위치는 긍정적
// meow[W]( ... ) : W와 volume, listener의 위치는 모두 부정적
// listener: Cat[U,T] : U의 위치는 긍정적, T의 위치는 부정적
// 결과 타입 Cat[Cat[U,T], U] 의 위치는 긍정적
// Cat[Cat[U,T], U] : Cat[U,T]의 위치는 부정적, U의 위치는 긍정적
// 결과 타입의 Cat[U,T] : U는 긍정적, T는 부정적
// T는 부정적인 위치에서만 사용, U는 긍정적 위치에서만 사용

하위 바운드

  • 타입 파라미터에 하위 바운드를 사용하면, 부정적인 위치의 타입을 공변적으로 만들 수 있음
class Queue[+T] (private val leading: List[T], private val trailing: List[T]) {
  def enqueue[U >: T](x: U) = new Queue[U](leading, x :: trailing)
  // U는 T의 수퍼 타입
  // ...
}

반공변성

trait OutputChannel[-T] {
  def write(x: T)
}

// OutputChannel[String]이 필요한 곳이라면 OutputChannel[AnyRef]를 바꿔놓아도 문제 없음
  • 리스코프 치환 원칙 : U타입의 값이 필요한 모든 경우를 T타입으로 대치할 수 있다면, T타입을 U타입의 서브타입으로 가정해도 안전함.
  • T가 U의 모든 연산을 지원하고, 모든 T의 연산이 그에 대응하는 U의 연산에 비해 더 적게 요구하고 더 많이 제공하는 경우 성립.
class Publication(val title: String)
class Book(title: String) extends Publication(title)
object Library {
  val books: Set[Book] =
    Set(
      new Book("Programming in Scala"),
      new Book("Walden")
    )
  def printBookList(info: Book => AnyRef) {
    for (book <- books) println(info(book)) // println은 인자에 toString을 호출해 출력
  }
}

object Customer extends Application {
  def getTitle(p: Publication): String = p.title
  Library.printBookList(getTitle)
}

객체의 비공개 데이터

  • 어떤 필드가 그 객체에서만 접근 가능하다면, 변성에 아무 문제를 일으키지 않음
class Queue[+T] private (
  private[this] var leading: List[T],
  private[this] var trailing: List[T]
) {
  private def mirror() =
    if (leading.isEmpty) {
      wihle (!trailing.isEmpty) {
        leading = trailing.head :: leading
        trailing = trailing.tail
      }
    }
  def head: T = {
    mirror()
    leading.head
  }
  def tail: Queue[T] = {
    mirror()
    new Queue(leading.tail, trailing)
  }
  def enqueue[U >: T](x: T) = new Queue[U](leading, x :: trailing)
}

상위 바운드

class Person(val firstName: String, val lastName: String) extends Ordered[Person] {
  def compare(that: Person) = {
    val lastNameComparison = lastName.compareToIgnoreCase(that.lastName)
    if (lastNameComparison != 0) lastNameComparison
    else firstName.compareToIgnoreCase(that.firstName)
  }

  override def toString = firstName +" "+ lastName
}

// T는 Ordered의 서브타입이어야 함
def orderedMergeSort[T <: Ordered[T]](xs: List[T]): List[T] = {
  def merge(xs: List[T], ys: List[T]): List[T] =
    (xs, ys) match {
      case (Nil, _) => ys
      case (_, Nil) => xs
      case (x :: xs1, y :: ys1) =>
        if (x < y) x :: merge(xs1, ys)
        else y :: merge(xs, ys1)
    }
  val n = xs.length / 2
  if (n == 0) xs
  else {
    val (ys, zs) = xs splitAt n
    merge(orderedMergeSort(ys), orderedMergesort(zs))
  }
}


+ Recent posts