代码改变世界

Scala 中的函数式编程基础(三)

2016-03-29 19:02  GarfieldEr007  阅读(368)  评论(0编辑  收藏  举报

主要来自 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
}

小记

这里是课程前四次的大概内容,因为第一次课是教你怎么安装,所以实际内容只有三次课,后面还有四次课。总体来说,函数式编程给人很多启发,但是如果不是真正需要用,也不宜占用太多时间去学习。暑假要去实习了,等下学期再学吧。

 

by:daniel-D
from:http://www.cnblogs.com/daniel-D/