2014/10/27

functional object in scala

通常認為 functional object 的特性,就是使用了 immutable object 的特性。

immutable vs mutable object

immutable objects 比 mutables obejcts 多了幾個優點, 但有一個缺點。

優點是

  1. immutable objects 比 mutable objects 容易理解,因為他們不會隨著時間改變。

  2. 可任意傳送 immutable objects,但如果是 mutable table,則可能需要複製一份,以防止被其他程式碼修改。

  3. 因為不能修改 immutable object 的內容,所以兩個 threads 不能同時存取一個 immutable object。這在 multi-thread 環境會很好用。

  4. immutable objects 可安全地用在 hash table key 裡面,才不會發生這種情況:當 mutable object 用在 HashSet,下次使用 HashSet 時,物件可能就不見了。

缺點是

  1. immutable objects 需要較大的一塊記憶體空間,進行物件複製。

製作一個 Rational Number Immutable Object

// primary constructor,需要兩個 整數,分別是分子與分母
class Rational(n: Int, d: Int) {

    // scala 會直接將 class body 裡面,不屬於任何 field/method 的程式碼
    // 直接放到 primary constructor

    // scala 限制更多, 只能讓 primary constructor,
    // 在第一行呼叫 superclass constructor

    // require 語法是 preconditions
    // 因為有理數的分母不能為 0,必須在建立物件時,加上欄位檢查
    require(d != 0)

    // 66/42 可約分成  11/7
    // 需要計算 gcd(66,42)   greatest common divisor
    private val g = gcd(n.abs, d.abs)

    // 外部可直接使用 r.numer, r.denom 兩個欄位的數值
    val numer = n / g
    val denom = d / g

    // auxiliary constructors
    // 5/1  原本要寫 Rational(5,1)   可省略成  Rational(5)
    // 任何 auxiliary constructor 都可以在第一行 呼叫其他 constructor
    def this(n: Int) = this(n, 1)

    // 有理數的加法,為了保持 immutable object 的特性
    // 運算後,回傳新的 Rational object
    // 定義 operator method
    def +(that: Rational): Rational =
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom)

    def +(i: Int): Rational =
        new Rational(numer + i * denom, denom)

    def -(that: Rational): Rational =
        new Rational(
            numer * that.denom - that.numer * denom,
            denom * that.denom)

    def -(i: Int): Rational =
        new Rational(numer - i * denom, denom)

    def *(that: Rational): Rational =
        new Rational(numer * that.numer, denom * that.denom)

    // Rational 要處理 r*2 的運算,必須要對 * 作 overloaded
    def *(i: Int): Rational =
        new Rational(numer * i, denom)

    def /(that: Rational): Rational =
        new Rational(numer * that.denom, denom * that.numer)

    def /(i: Int): Rational =
        new Rational(numer, denom * i)

    // 列印物件時,會自動呼叫 toString,否則預設列印出物件的 reference 位址
    // 這裡是覆寫 override 上層的 toString
    override def toString = numer + "/" + denom

    // gcd 最大公因數 greatest common divisor
    private def gcd(a: Int, b: Int): Int =
        if (b == 0) a else gcd(b, a % b)
}

identifier in scala

有四種

  1. alphanumeric identifier
    或 a letter 開始, 後面可以是 letters/digits/,$ 也算是一個 character,但這是保留給 scala compiler 使用,建議不要使用 $ , 以免跟 compiler 產生的 id 有衝突。

  2. operator identifier
    包含 1~多 個 operator characters (ex: + : ? ~ # )
    ex: + ++ ::: <?> :->

  3. mixed ifentifier
    包含 a alphanumberic identifier, 後面是 , 然後是 operator identifier。
    ex: unary
    + myvar_=

  4. literal identifier
    用 (...) 包含在裡面的任意 string
    ex: x <clinit> yield
    yield 是 java Thread class 的 static method,但在 scala 不能寫 Thread.yeild(),因為 yield 是 scala 的保留字,所以要改寫成 Thread.yield()。

implicit conversion

上面的 Rational 可支援 r2,但是無法支援 2r 的計算,為了解決這個問題,scala 讓我們建立一個 implicit conversion,自動轉換 integer 為 rational number。

implicit conversion 定義有 scope 的限制,如果要在 scala interpreter 裡面使用,則必須在 interpreter 裡面執行 implicit conversion 的定義。如果把 implicit method 定義放在 Rational class 裡面,則對 inpterpreter 來說,是沒有作用的。

implicit def intToRational(x: Int) = new Rational(x)

測試

scala> val r = new Rational(2,3)
r: Rational = 2/3

scala> 2 * r
res0: Rational = 4/3

雖然 implicit conversion 很好用,但可能會造成程式可讀性降低。

scala 的程式特性是簡潔,但在簡化程式碼的時候,同時要考慮到 readable 與 understandable 的要求,才不至於寫出難以維護的程式碼。