List是一种最普通的泛函数据结构,比较直观,有良好的示范基础。List就像一个管子,里面可以装载一长条任何类型的东西。如需要对管子里的东西进行处理,则必须在管子内按直线顺序一个一个的来,这符合泛函编程的风格。与其它的泛函数据结构设计思路一样,设计List时先考虑List的两种状态:空或不为空两种类型。这两种类型可以用case class 来表现:
1 trait List[+A] {} 2 case class Cons[+A](head: A, tail: List[A]) extends List[A] 3 case object Nil extends List[Nothing]
以上是一个可以装载A类型元素的List,是一个多态的类型(Polymorphic Type)。+A表示List是协变(Covariant)的,意思是如果apple是fruit的子类(subtype)那么List[apple]就是List[fruit]的子类。Nil继承了List[Nothing],Nothing是所有类型的子类。结合协变性质,Nil可以被视为List[Int],List[String]...
List的另一种实现方式:
1 trait List[+A] { 2 def node: Option[(A, List[A])] 3 def isEmpty = node.isEmpty 4 } 5 object List { 6 def empty[A] = new List[A] { def node = None} 7 def cons[A](head: A, tail: List[A]) = new List[A] { def node = Some((head, tail))} 8 }
以上代码中empty,cons两个方法可以实现List的两个状态。
我们还是采用第一种实现方式来进行下面有关List数据运算的示范。第二种方式留待Stream的具体实现示范说明。
先来个List自由构建器:可以用List(1,2,3)这种形式构建List:
1 object List { 2 def apply[A](as: A*): List[A] = { 3 if (as.isEmpty) Nil 4 else Cons(as.head,apply(as.tail:_*)) 5 } 6 }
说明:使用了递归算法来处理可变数量的输入参数。apply的传入参数as是个数组Array[A],我们使用了Scala标准集合库Array的方法:as.head, as.tail。示范如下:
1 scala> Array(1,2,3).head 2 res11: Int = 1 3 4 scala> Array(1,2,3).tail 5 res12: Array[Int] = Array(2, 3)
增加了apply方法后示范一下List的构成:
1 val li = List(1,2,3) //> li : ch3.list.List[Int] = Cons(1,Cons(2,Cons(3,Nil))) 2 val ls = List("one","two","three") //> ls : ch3.list.List[String] = Cons(one,Cons(two,Cons(three,Nil)))
与以下方式对比,写法简洁多了:
1 val lInt = Cons(1,Cons(2,Cons(3,Nil))) //> lInt : ch3.list.Cons[Int] = Cons(1,Cons(2,Cons(3,Nil)))
再来试一个运算:计算List[Int]里所有元素的和,还是用模式匹配和递归方式来写:
1 trait List[+A] { 2 def sum: Int = this match { 3 case Nil => 0 4 case Cons(h: Int,t: List[Int]) => h + t.sum 5 } 6 }
我们把sum的实现放到特质申明里就可以用以下简洁的表达方式了:
1 List(1,2,3) sum //> res0: Int = 6
再试着玩多态函数sum:
1 def sum[B >: A](z: B)(f: (B,B) => B): B = this match { 2 case Nil => z 3 case Cons(h,t) => f(h, t.sum(z)(f)) 4 }
现在可以分别试试List[Int]和List[String]:
1 List(1,2,3).sum(0){_ + _} //> res0: Int = 6 2 List("hello",",","World","!").sum(""){_ + _} //> res1: String = hello,World!
以下是一些List常用的函数:
1 trait List[+A] { 2 3 def head: A = this match { 4 case Nil => sys.error("Empty List!") 5 case Cons(h,t) => h 6 } 7 def tail: List[A] = this match { 8 case Nil => sys.error("Empty List!") 9 case Cons(h,t) => t 10 } 11 def take(n: Int): List[A] = n match { 12 case k if(k<0) => sys.error("index < 0 !") 13 case 0 => Nil 14 case _ => this match { 15 case Nil => Nil 16 case Cons(h,t) => Cons(h,t.take(n-1)) 17 } 18 } 19 def takeWhile(f: A => Boolean): List[A] = this match { 20 case Nil => Nil 21 case Cons(h,t) => if(f(h)) Cons(h,t.takeWhile(f)) else Nil 22 } 23 def drop(n: Int): List[A] = n match { 24 case k if(k<0) => sys.error("index < 0 !") 25 case 0 => this 26 case _ => this match { 27 case Nil => Nil 28 case Cons(h,t) => t.drop(n-1) 29 } 30 } 31 def dropWhile(f: A => Boolean): List[A] = this match { 32 case Nil => Nil 33 case Cons(h,t) => if (f(h)) t.dropWhile(f) else this 34 } 35 }
看看以上的这些函数;是不是都比较相似?那是因为都是泛函编程风格的原因。主要以模式匹配和递归算法来实现。以下是使用示范:
1 List(1,2,3).head //> res0: Int = 1 2 List(1,2,3).tail //> res1: ch3.list.List[Int] = Cons(2,Cons(3,Nil)) 3 List(1,2,3).take(2) //> res2: ch3.list.List[Int] = Cons(1,Cons(2,Nil)) 4 List(1,2,3).takeWhile(x => x < 3) //> res3: ch3.list.List[Int] = Cons(1,Cons(2,Nil)) 5 List(1,2,3) takeWhile {_ < 3} //> res4: ch3.list.List[Int] = Cons(1,Cons(2,Nil)) 6 List(1,2,3).drop(2) //> res5: ch3.list.List[Int] = Cons(3,Nil) 7 List(1,2,3).dropWhile(x => x < 3) //> res6: ch3.list.List[Int] = Cons(3,Nil) 8 List(1,2,3) dropWhile {_ < 3} //> res7: ch3.list.List[Int] = Cons(3,Nil)
试试把一个List拼在另一个List后面:
1 def ++[B >: A](a: List[B]): List[B] = this match { 2 case Nil => a 3 case Cons(h,t) => Cons(h,t.++(a)) 4 }
1 ist(1,2) ++ List(3,4) //> res8: ch3.list.List[Int] = Cons(1,Cons(2,Cons(3,Cons(4,Nil))))
只是想试试Scala的简洁表达方式。
噢,漏了两个:
1 def init: List[A] = this match { 2 case Cons(_,Nil) => Nil 3 case Cons(h,t) => Cons(h,t.init) 4 } 5 def length: Int = this match { 6 case Nil => 0 7 case Cons(h,t) => 1 + t.length 8 }
1 List(1,2,3).init //> res9: ch3.list.List[Int] = Cons(1,Cons(2,Nil)) 2 List(1,2,3).length //> res10: Int = 3
下面把几个泛函数据结构通用的函数实现一下:
1 def map[B](f: A => B): List[B] = this match { 2 case Nil => Nil 3 case Cons(h,t) => Cons(f(h),( t map f)) 4 } 5 def flatMap[B]( f: A => List[B]): List[B] = this match { 6 case Nil => Nil 7 case Cons(h,t) => f(h) ++ ( t flatMap f ) 8 } 9 def filter(f: A => Boolean): List[A] = this match { 10 case Nil => Nil 11 case Cons(h,t) => if (f(h)) Cons(h,t.filter(f)) else t.filter(f) 12 }
1 List(1,2,3) map {_ + 10} //> res13: ch3.list.List[Int] = Cons(11,Cons(12,Cons(13,Nil))) 2 List(1,2,3) flatMap {x => List(x+10)} //> res14: ch3.list.List[Int] = Cons(11,Cons(12,Cons(13,Nil))) 3 List(1,2,3) filter {_ != 2} //> res15: ch3.list.List[Int] = Cons(1,Cons(3,Nil))
这几个函数有多种实现方法,使Scala for-comprehension对支持的数据结构得以实现。有关这几个函数在泛函编程里的原理和意义在后面的有关Functor,Applicative,Monad课题里细说。