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 的要求,才不至於寫出難以維護的程式碼。

2014年10月20日

迷你書 阿里巴巴的技術力量

迷你書 阿里巴巴的技術力量

因應阿里巴巴在美國上市的重大消息,InfoQ 將一些對阿里巴巴工程師的專訪,集結成一本迷你書 阿里巴巴上市背後的技術力量,我們可以從這些互動的 FAQ 訪問中,了解到這個 公司的文化與態度。

阿里巴巴的目標,是要幫助大陸的中小企業,讓他們也能很快地使用網路平台進行銷售與交易,平台使用的關鍵問題在使用者,而且是關於賣方與買方雙方面的問題。

甚至連技術深度導向的核心業務優化團隊,同樣也要面對使用者,開發者必須要將自己的優化成果推廣出去,系統夠好也要有人使用,除非你開發的東西已經超過目前全人類的想像,已經開發到了未來。

在政治集權的市場中,建立互信金流機制

在阿里巴巴上市現場,記者提出對 trust 的疑問,阿里巴巴如何說服美國投資者,面對中共政權,如何讓投資者相信,這是一個可以被信任的企業。

馬雲說明,阿里巴巴為了提供一個讓中小企業可以使用的 ecosystem,必須跟中共政權對話,建立互信基礎,這是必要的,政府跟企業必須合作,才能在非常糟糕的信用卡市場中,完成一個網路交易平台。 Every trust takes time to build.

架構師的歷程

要成為一個架構師,更重要的學習是來自於實踐,所謂一個行業的專家指的並不是他能力有多強,是指他碰到過了這個行業裡面所有的問題,同時他解決了,他就能成為專家。

架構師不僅僅要了解技術,同時要進行業務分析,更全面地了解問題與方法,才能更容易去掌握問題的全貌。

淘寶的架構變革

  1. 2003/5 ~ 2004/5 LAMP
  2. 2004/2 ~ 2008/3 weblogic -> JBoss,開發了 TFS, iSearch, TDBM, CDN
  3. 2007/10 ~ 2009/11 系統走向產品化、服務化,支援大型團隊的並行開發,逐步模組化、中心化、可快速擴充、提昇可用性。非核心資料庫由 Oracle 移至 MySQL,建立訊息系統與服務框架,淘寶開放平台(TOP)上線
  4. 2009/8 ~ now 逐步提供系統自動化,減少操作失誤機率

資料庫的演進

阿里巴巴一開始是使用 Oracle,發展至今,已經慢慢地藉由拆分的方法,移轉到 MySQL,至於 NoSQL 則是依照業務的需求而使用,因為阿里巴巴的核心業務比較複雜,並不適合直接改用 NoSQL,而是在適當的業務場景中,採用 NoSQL。

當資料庫性能不足時,就會考慮使用 Cache,目前有集中式與分散式兩種 Cache 解決方案,也有使用 memory cache。數據資料由集中式演進為分散式,可跨多個 IDC 進行容錯備份,數據資料異地同步。

拆分資料庫有專責單位,最重要的任務是,如何在拆分的過程中,達成不間斷服務,平順地過渡到新架構上。第一次拆分最大的 table,花了兩年的時間,現在進步到2~3個月。

企業訊息系統

訊息系統對阿里巴巴來說,是非常重要的,但是阿里巴巴沒有直接採用既有的訊息系統,反而選擇以 KafKa 的概念,重新實作一個,最重要的原因是「系統維護」,因為阿里巴巴是使用 Java 開發語言,而 KafKa 是用 Scala 開發的。

訊息系統最重要的是要維護資料的一致性,目前的規模是每天處理百億個訊息,系統 Loading 在 4~5 左右。

前端性能優化的重點

  1. 減少 http request
  2. 減少 redirect
  3. preload 資源
  4. 盡量減少 cookie 的大小
  5. delay loading
  6. asynchronous ajax,減少 DOM 節點數,加快render與 first byte時間
  7. CDN 加速
  8. delay render
  9. pre-resolve DNS
  10. js 不放在 header,避免 blocking concurrent render tasks

系統必須根據網站的特點進行優化,每個網站的轉換率、流量與使用者的特性不同,必須根據實際的數據,判斷優化是否對轉換率及流量是有幫助的,並不是無止境的優化。

JVM 團隊

核心系統開發部一開始,是在集團內部尋找需求,優化後,得到十倍的效能提昇,漸漸地累積出優化成果,內部其他團隊就會自動找開發部協助解決效能問題。

優化的方式,並不是要求應用程式修改程式碼,而是直接針對 JVM 進行修改與調整。但如果是計算密集的應用,就要用調整演算法的方式處理。

因為有優化 JVM 的經驗的工程師不多,因此這個單位大多都是由應屆畢業生進行專業培養。

這個單位的 KPI 不單純只看優化的結果,重要的是要對優化結果,找到適當的應用,把成果推廣出去,但有時候會因為各種原因,而用不上你修改後的東西。

Open Source

雖然阿里巴巴將自身開發的一些軟體以 Open Source 的方式發布出來,但對於阿里雲飛天平台,企業認為這是阿里巴巴的核心業務與價值,因此沒有開源。

開源對阿里巴巴來說,並不是一個核心企業價值,阿里巴巴是個需要賺錢的公司,這些開源專案,都是因為跟某些原始專案的團隊搭配上的問題,才刻意 fork 並調整的專案,而這些修改,都是因應阿里巴巴公司規模成長業務需要而去做的。

2014年10月13日

scala: 關於 basic types and operations 的特殊語法

triple quote string

Java 語言在定義 String 的時候,總是會遇到一個麻煩的問題,那就是 escape,尤其是在定義 regular expression pattern 的時候,escape character 的氾濫,把原始的 String 定義破壞殆盡。

scala 借用了 erlang 的想法,引入 triple quote,用來定義使用了特殊字元的 string,非常地好用。

scala> val escapes = "\\\"\'"
escapes: java.lang.String = \"'

因為用 \ escape 會讓字串看起來很糟糕,這時候可以用 三個 " 來作為字串的開頭 & 結尾,字串中間都可以直接使用 " 或是 '。

println("""Welcome to The Maxtix.
             Type "HELP" for help.""")

在 scala cli 中測試結果為

scala> println("""Welcome to The Maxtix.
     |              Type "HELP" for help.""")
Welcome to The Maxtix.
             Type "HELP" for help.

但因為上面那個 statement 有換行,第二行前面多了一些 空白字元,為了要對齊前面,在第一與第二行最前面加上 | ,然後呼叫字串的 stripMargin。

println("""|Welcome to The Maxtix.
           |Type "HELP" for help.""".stripMargin)

在 scala cli 中測試結果為

scala> println("""|Welcome to The Maxtix.
     |            |Type "HELP" for help.""".stripMargin)
Welcome to The Maxtix.
Type "HELP" for help.

operators are methods

簡單的 sum = 1+2,其中用到了一個 + operator。

scala> val sum = 1 + 2
sum: Int = 3

但對於 scala 來說,1+2 實際上是呼叫了 (1).+(2)

scala> val sumMore = (1).+(2)
sumMore: Int = 3

乍看之下有點奇怪,但其實先假設 + 是一個 class 的 method: plus,因為呼叫 plus,我們必須寫成

(1).plus(2)

再把 plus 換回 + 就變成

(1).+(2)

對 scala 來說,任何 method 都可以當作 operator,s.indexOf('o') 可以寫成 s indexOf 'o'

scala> val s = "Hello, world!"
s: java.lang.String = Hello, world!

scala> s indexOf 'o'     // Scala invokes s.indexOf('o')
res0: Int = 4

scala> s indexOf ('o', 5) // Scala invokes s.indexOf('o', 5)
res1: Int = 8

unary operator

減號 - 在 scala 是一種 unary operator,就是只需要一個 operand 的 operator,他會轉換為 unary_-

scala> -2.0
res2: Double = -2.0

scala> (2.0).unary_-
res3: Double = -2.0

scala> +3
res4: Int = 3

scala> (3).unary_+
res5: Int = 3

unary+ 是相對於 unary- 而存在的,實際上 unary_+ 並沒有作任何事情。

省略的參數

如果 () 裡面沒有任何參數,就可以省略不寫

scala> val s = "Hello, world!"
s: String = Hello, world!

scala> s.toLowerCase
res0: String = hello, world!

scala> s toLowerCase
warning: there was one feature warning; re-run with -feature for details
res1: String = hello, world!

== 在 scala 跟 java 的 差異

Java 的 == 是 reference equality,兩個變數指向 heap 裡面同一個 object 。

scala 的 == 比較接近 java 的 equals,但另外提供 eq ne 這個 method 比較 reference equality。

因為 Java 的 == 同時用在 primitive data type 與 object 的比較上,因此常常會搞混。

scala 將 == 用在實際比較兩個 object 的 value,因為需要用到比較 reference 的狀況是非常少的。

2014年10月6日

Classes and Objects in Scala

Scala 特地將 object 從 class 中取出來,讓 object 直接作為 singleton object 的保留字,原本在 Java 中需要透過 Singleton Design Pattern 才能達成的工作,Scala 內建了 singleton object,我們不需要再去了解 Design Pattern,只需要專注在開發 object 內容的工作上。

static method

在 object 中定義可以使用的 method,實際上用起來就像是在 Java 呼叫 static method 一樣。通常我們會在撰寫 Utility 程式中,用到 singleton 與 static method 的想法去實做,畢竟使用公用程式還要先產生物件,會覺得非常麻煩。

object StringUtil {
    def trimHead(s:String) = s.dropWhile(_ == ' ')

    def main(args: Array[String]):Unit = {
        println(StringUtil.trimHead(args(0)))
    }
}

測試

>scala testscala.StringUtil "   test  22 "
test  22

companion object

如果在同一個 scala 檔案中,包含了同樣名字的 class 與 object 兩個定義,在 scala 就稱為 companion object。

class StringUtil {
    val s = "     I am a string in class StringUtil"
}

object StringUtil {
    def trimHead(s: String) = s.dropWhile(_ == ' ')
    def getClassString(): String = {
        val su = new StringUtil()
        trimHead(su.s)
    }

    def main(args: Array[String]): Unit = {
        println(StringUtil.trimHead(args(0)))
        println(StringUtil.getClassString())
    }
}

如果使用 StringUtil.xxx ,實際上是呼叫 singleton object StringUtil,如果是 val su = new StringUtil(),實際上是產生了 StringUtil class 的 instance。

因此 su 是沒辦法呼叫 trimHead 這個定義在 object 的 method,而 StringUtil.s 也同樣是沒有辦法使用的。

仔細看一下 companion object,其實就跟定義一個 Java class,裡面寫上幾個 static method/field 完全一樣,scala 明確地用 object 與 class 兩個關鍵字,將 class/object 內部的靜態與一般區塊分開。

對於programmer來說,有了更簡潔的語法,更簡短的程式碼,又不需要了解 Singleton Design Pattern,整體來說,有什麼道理,不該繼續往 scala 前進呢?

Scala IDE in Eclipse 4.4 Luna

另外再提供一個資訊,目前 Scala IDE 正式版只能在 Eclipse 4.3 Kepler 上運作,如果是使用 Eclipse 4.4 Luna,則我們得要改用開發版的 Scala IDE。

雖然Scala IDE 4.0.0 Milestone 3在網頁上說可以用在 Eclipse Luna,不過實際上測試後是不行的,必須要使用Scala IDE Lithium (4.0), Nightly版本。