Scala 中的函数式编程基础(三)
主要来自 Scala 语言发明人 Martin Odersky 教授的 Coursera 课程 《Functional Programming Principles in Scala》。
3. Data and Abstraction
3.1 Class Hierarchies
这一集字幕不同步-,-
,听得有点费力!
类的概念和其他语言里面很相似,基类,子类,父类啥的叫法差不多。在 Scala 中,所有用户自定义的类都是另外一个类的子类,如果没有显式给定父类,java 里面默认继承 java.lang,scala 里面是 Object。无论基类中的方法有没有具体实现,子类都可以用 override
重新定义,回想起之前强大的 toString
了吗?
举一个二叉树的例子:
package week3
object insets {
val t1 = new NonEmpty(3, Empty, Empty) //> t1 : week3.NonEmpty = {.3.}
val t2 = t1 incl 4 //> t2 : week3.IntSet = {.3{.4.}}
val t3 = new NonEmpty(5, Empty, Empty) //> t3 : week3.NonEmpty = {.5.}
t2 union t3 //> res0: week3.IntSet = {{{.3.}4.}5.}
}
abstract class IntSet { // 抽象类作为基类,无法实例化,定义了三个接口
def contains(x: Int): Boolean // 查找是否包含 x
def incl(x: Int): IntSet // 如果 x 不存在,将 x 放入二叉树中
def union(x: IntSet): IntSet // 两棵树融合
}
object Empty extends IntSet { // Empty 是 IntSet 的 subclass,`object` 表示单例模式,所有空节点都可以用一个对象来表示
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = new NonEmpty(x, Empty, Empty)
def union(other: IntSet): IntSet = other
override def toString = "." // 空节点打印"."
}
class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet {
def contains(x: Int): Boolean =
if (x < elem) left contains x
else if (x > elem) right contains x
else true
def incl(x: Int): IntSet =
// 实际上创建了一个新树,新树和旧树共用未改变的子树
// 这个叫 persistent data structure,是把函数式编程扩展到 collections 的关键之一
// 反正他是这么说的 `-,-`
if (x < elem) new NonEmpty(elem, left incl x, right) // 一重一重地复制节点
else if (x > elem) new NonEmpty(elem, left, right incl x)
else this
def union(other: IntSet): IntSet =
((left union right) union other) incl elem //
override def toString = "{" + left + elem + right + "}" //强大的递归啊
}
3.2 How Classes Are Organized
没学过 java,估计和 java 中 package 管理一样。
在源码最顶端写上 package week3
表示这个文件的 object 或者 class 属于这个包。要使用某一个类,可以在源码中用全名 week3.classA
,也可以像 python 一样在最开始 import,源码中间用类名:
- import week3.classA:导入类 classA
- import week3.{classA, classB}:导入两个类
- import week3._ :导入包所有(通配符导入方法)
除了从包导入,还可以从 object 导入。所有 Scala 程序默认导入一些 entities,比如 scala 中的 Int,java.lang 中的 Object,scala.Predef 中的断言等。更多信息可以查看 scala 的标准库。
在 java 和 scala 中,一个类只能有一个父类(单继承),如何实现多继承,scala 中采用 traits。trait 像 java 里面的接口,偏抽象,但是更强大,可以包含 field 和具体方法,但是不能有value参数。子类可只能继承一个父类,但是可以继承任意多个 traits,例如:class Square extends Shape with Planar with Moveble
。
scala 类型结构如下,实线表示继承,虚线表示隐式转化。
Any
是所有类型的基本类,包含的方法有:‘==’,‘!=’,‘equals’,‘hashCode’,‘toString’AnyVal
是数值类型的基本类。AnyRef
是所有引用类型的基本类,也是 java.lang.Object 的别名。Nothing
是所有类的子类型。主要作用是异常类与collection中的一个空元素。Null
是所有类的子类型。但是与 AnyVal 的子类型不兼容。
Q:if (true) 1 else False
的返回类型是什么?
A:int 和 boolean 类型,返回父类 AnyVal。
3.3 Polymorphism
Polymorphism 意味着函数可以以多种类型出现。一张 PPT 总结 Polymorphism:
假设我们要建立一个 list 类,它可能包含了不同的数据类型(整数,布尔,list自身类型等),例子如下:
这时需要用泛型来表示。新建一个package叫week4,在其中新建一个 trait。它的两个‘子类’分别为 Cons 和 Nil,分别表示含有元素的节点和空节点。
package week4
// [T] 是类型参数,比如int,double之类。是泛型编程的基础
trait List[T] {
def isEmpty: Boolean
def head: T
def tail: List[T]
}
class Cons[T](val head: T, val tail: List[T]) extends List[T] {
def isEmpty = false
// head 和 tail 已经在初始化中实现
}
class Nil[T] extends List[T] {
def isEmpty = true
def head: Nothing = throw new NoSuchElementException("Nil.head")
// Nothing 是任何类型的子类,所以也是 T 的子类
def tail: Nothing = throw new NoSuchElementException("Nil.tail")
}
在 week4 中新建一个 scala worksheet,测试一下上述代码:
package week4
import week4._
object nth {
// 创建一个含有一个元素的 list
def singleton[T](elem: T) = new Cons(elem, new Nil)
//> singleton: [T](elem: T)week4.Cons[T]
singleton[Int](3) //> res0: week4.Cons[Int] = week4.Cons@71be98f5
singleton(3) // 编译器可以从 3 推到出 T 是 Int
// 寻找 list 中的第 n 个元素
def nth[T](n: Int, xs: List[T]): T =
if (xs.isEmpty) throw new IndexOutOfBoundsException
else if (n == 0) xs.head
else nth(n - 1, xs.tail) //> nth: [T](n: Int, xs: week4.List[T])T
// 创建一个 list = [1, 2, 3]
val list = new Cons(1, new Cons(2, new Cons(3, new Nil)))
nth(2, list) //> res2: Int = 3
}
小记
这里是课程前四次的大概内容,因为第一次课是教你怎么安装,所以实际内容只有三次课,后面还有四次课。总体来说,函数式编程给人很多启发,但是如果不是真正需要用,也不宜占用太多时间去学习。暑假要去实习了,等下学期再学吧。