在函數式設計中,遞歸是一種重要的思維。本文通過List
的實現為例,闡述Scala
在設計具有「不變性」數據結構的思路和技巧。
遞歸的數據結構
sealed abstract class List[+A]
final case class ::[A](head: A, tail: List[A]) extends List[A]
final case object Nil extends List[Nothing]
遞歸的的算法
sealed abstract class List[+A] {
...
def map[B](f: A => B): List[B] = this match {
case Nil => Nil
case h :: t => f(h) :: t.map(f)
}
def filter(f: A => Boolean): List[A] = this match {
case Nil => Nil
case h :: t => if(f(h)) h :: t.filter(f) else t.filter(f)
}
def foreach[U](f: A => U): Unit = this match {
case Nil => ()
case h :: t => { f(h); t.foreach(f) }
}
def forall(f: A => Boolean): Boolean = this match {
case Nil => true
case h :: t => f(h) && t.forall(f)
}
def exists(f: A => Boolean): Boolean = this match {
case Nil => false
case h :: t => f(h) || t.exists(f)
}
}
最終類
不能在類定義的文件之外定義List
的任何新的子類。List
只有兩個子類:
case class ::[A]
case object Nil
這使得List.map, filter, foreach
等方法可以使用「模式匹配」的原因。
協變
List[+A]
的類型參數是「協變」的。
-
Nothing
是任何類的子類; -
List[Nothing]
,或Nil
也是List[A]
的子類;
:
結尾的方法
定義::
的Cons
操作,其具有特殊的結合性;
結合性
sealed abstract class List[+A] {
def ::[B >: A] (x: B): List[B] = new ::(x, this)
...
}
:
結尾的方法,Scala
具有特殊的結合性。
1 :: 2 :: 3 :: Nil // List(1, 2, 3)
等價于:
Nil.::(3).::(2).::(1) // List(1, 2, 3)
逆變點
參數x
在List
方法::
中定義本來是一個「逆變點」,這與List[+A]
的協變相矛盾,為此通過提供類型「下界」,并保證其「不變性」,使得這兩個現象得以和諧。
def ::[B >: A] (x: B): List[B] = new ::(x, this)