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))
}
}
'Developing' 카테고리의 다른 글
Programming in Scala 스터디 정리 - 20장. 추상 멤버 (0) | 2017.10.09 |
---|---|
Programming in Scala 스터디 정리 - 18장. 상태가 있는 객체 (0) | 2017.09.30 |
Programming in Scala 스터디 정리 - 17장. 컬렉션 (0) | 2017.09.28 |