함수형 데이터 구조는 불변으로 정의하는 것이다.
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
object List {
def sum(ints: List[Int]): Int = ints match {
case Nil => 0
case Cons(x: Int, xs: List[Int]) => x + sum(xs)
}
def product(ds: List[Double]): Double = ds match {
case Nil => 1.0
case Cons(0.0, _) => 0.0
case Cons(x, xs) => x * product(xs)
}
def apply[A](as: A*): List[A] =
if (as.isEmpty) Nil
else Cons(as.head, apply(as.tail: _*))
}
트레이트(trait)는 부가적으로 메소드의 구현을 가질 수 있는 추상 인터페이스 이다.
리스트가 비어 있음은 Nil로, 비어있지 않을 때는 Cons로 나타낼 수 있다.
Cons는 초기 요소, head, tail이라고 부르는 리스트의 나머지 요소로 구성된다.
타입 파라미터 A앞에 +는 변동 애노테이션으로 이 기호는 A가 공변성 또는 List의 "가능한" 파라미터 임을 나타내는 것이다.
List[Dog]는 List[Animal]의 서브타입으로 간주된다.
Nil이 List[Nothing]을 상속하는 것에 주목하자. Nothing은 모든 유형의 서브타입이므로 변동 애노테이션과 함께 Nil은 정확하게 List [Int], List [Double] 등으로 간주 될 수 있다.
동반 객체(companion object) class나 trait와 동일한 이름을 사용하는 객체이고, 동일한 소스 파일에 정의된다. 동반 객체는 다른 객체는 접근할 수 없지만 적절한 class/trait에는 접근할 수 있어서 다른 객체와는 차이점이 있다. 특히 class/trait의 private 필드나 메소드에 접근할 수 있다.
class A(d: String) {
private var a = ""
override def toString = d + a;
}
object A {
def apply(b:String, e:String) = {
val a = new A(b)
a.a = e
a
}
}
case class B()
object B {
def apply() = {
val a = new A("")
//can not access a.a
new B()
}
}
패턴 매치는 표현식의 구조로 내려갈 수있는 멋진 switch 문처럼 작동하여 해당 구조의 하위 표현식을 검사하고 추출한다.
-
List(1,2,3) match { case _ ⇒ 42 } 결과는 42. 변수 패턴으로 매치한 것으로 _는 어떤 표현식과도 매치된다.
-
List(1,2,3) match { case Cons(h,_) ⇒ h } 결과는 1
-
List(1,2,3) match { case Cons(_,t) ⇒ t } 결과는 List(2,3)
-
List(1,2,3) match { case Nil ⇒ 42 } 결과는 실행시 MatchError 를 발생한다. MatchError는 매치 표현식에서 타겟에 매치된 것이 없을 때를 나타낸다.
객체의 리스트를 적용하는 함수를 variadic 함수라 하고, 0개 또는 더 많은 A타입의 매개변수를 설정할 수 있다 특별한 타입 애노테이션 _*은 variadic method로 Seq를 전달 할 수 있도록 한다.
실제 자료는 불변(immutuable). 복사나 수정없이 목록 자료를 재사용하면 된다. 자료구조에 연산이 가해져도 기존 참조들은 변하지 않는다.
함수적 자료구조는 영속적(persistent)
head -> (a, link) -> (b, link) -> (c, link) -> (d, Nil)
val first = head
=> List(a, b, c, d)
val second = head.link
=> List(b, c, d)
def append[A](a1: List[A], a2: List[A]): List[A] =
a1 match {
case Nil => a2
case Cons(h,t) => Cons(h, apeend(t, a2))
}
해석 : 현재 함수의 실행 시간과 메모리 사용량은 오직 a1의 길이에 의존. 이후는 단순 a2를 가르킨다.
def init[A](l: List[A]): List[A] = {
l match {
case Cons(h, Nil) => Nil
case Cons(h, t) => Cons(h, init(t))
}
}
해석 : Cons의 tail 을 치환할 때마다 반드시 이전의 모든 Cons 객체를 복사해야 한다.
def dropWhile[A](l: List[A], f: A => Boolean): List[A]
dropWhile(xs, (x: Int) => x < 4)
=> x 의 parameter 정의가 필요함.
def foldRight[A, B](as: List[A], z: B)(f: (A, B) => B): B =
as match {
case Nil => z
case Cons(x, xs) => f(x, fildRight(xs, z)(f))
}
def sum2(ns: List[Int]) =
foldRight(ns, 0)((x,y) => x + y)
def product2(ns: List[Double]) =
foldRight(ns, 1.0)(_ * _)
-
def take(n: Int): List[A] this의 처음 n개의 요소들로 이루어진 목록을 돌려준다.
-
def takeWhile(f: A ⇒ Boolean): List[A] 주어진 술어 f를 만족하는, this의 가장 긴 선행 요소들로 이루어진 목록을 돌려준다.
-
def forall(f: A ⇒ Boolean): Boolean this의 모든 요소가 술어 f를 만족할 때에만 true를 돌려준다.
-
def exists(f: A ⇒ Boolean): Boolean this의 요소들 중 하나라도 f를 만족하는 것이 있으면 true를 돌려준다.
-
scanLeft와 scanRight foldLeft 및 foldRight와 비슷하되 최종적으로 누적된 값만 돌려주는 것이 아니라 부분 결과들의 List를 돌려준다.