Scala: 感覺像動態的靜態語言

banq發表於2011-06-14
Weblogs Forum - Scala: The Static Language that Feels Dynamic

著名的"Thinking in Java"作者Bruce Eckel 在artima.com寫了這篇Scala入門篇(banq:artima的論壇和Jdon的論壇都源於同一個宗主Jive,所以你覺得兩者介面很象)。

下面大概意譯一下Bruce Eckel這篇文章:
Bruce Eckel 認為scala雖然是靜態語言,但感覺非常像Python,
(Bruce Eckel用語很感性,不象Thinking,而象feeling)。

class Building
val b = new Building
<p class="indent">

val是不可變數,這樣能夠使得併發編碼更容易一些(var也有變數的).這裡沒有必要在b之前象Java那樣宣告一下型別,因為scala能夠分辨出型別。


class Building(feet: Int) {
  val squareFeet = feet
}
val b = new Building(100)
println(b.squareFeet)
<p class="indent">

println列印輸出不必使用類似Java的System.out了,類中欄位預設是public的,這樣我們可以使用b.squareFeet直接訪問其類中欄位。當然你也可以設定private。

class Building(val feet: Int)
val b = new Building(100)
println(b.feet)
<p class="indent">

這段和上面區別是b.feet, feet能夠自動成為類的欄位。


case class Building(feet: Int)
val b = Building(100)
println(b) // Result: Building(100)
<p class="indent">

這段程式碼主要是開始的case class ,引數自動成為欄位,引數之前也不用加val了。

val m = Map(Building(5000) -> "Big", Building(900) -> "Small", Building(2500) -> "Medium")
m(Building(900)) // Result: Small
<p class="indent">

scala中使用->來表示map的鍵和值。


class House(feet: Int) extends Building(feet)
val h = new House(100)
println(h.feet) // Result: 100
<p class="indent">

展示了extends繼承,但是注意extends後面的父類寫法。


trait Bathroom
trait Kitchen
trait Bedroom {
  def occupants() = { 1 }
}
class House(feet: Int) extends Building(feet) with Bathroom with Kitchen with Bedroom
var h = new House(100
val o = h.occupants()
val feet = h.feet
<p class="indent">

traits是將行為混合在一起,類似過去的AOP等,被認為用來實現DCI架構較好的語言方式,這裡的trait非常類似Java中的介面,但比介面要豐富,可以自己定義方法occupants,其def是用來定義方法名稱的,等於號後面是方法體。

def occupants() = { 1 }表示返回值int型別1,如果要更加規範如下:
def occupants(): Int = { 1 }

注意occupants已經成為House的一部分,這就是traits的mixin力量,而且這個mixin的效能要比Java好多。

以上是簡單的語法。

函式程式設計方式functional programming
比如我們有一個集合資料:

val v = Vector(1.1, 2.2, 3.3, 4.4)
<p class="indent">

透過下面語法可以輸出列印:
for(n <- v) {
println(n)
}

注意:<- 前面的n能夠得到v集合中每個值。

還有更簡單的:v.foreach(println) (Java中引數一般是變數,不會是方法等,這裡是方法println)

別看這麼簡單,它是利用的函式程式設計的優點,實際是一個匿名函式,完整的基本形式:

( function parameters ) => function body
<p class="indent">

=>意味著:將左邊的引數應用到右邊的程式碼中。
另外一個匿名函式案例:
(x:Int, y:Double) => x * y

之前的foreach ,實際完整的如下:
v.foreach((n:Double) => println(n))

通常情況下,scala能夠看到v中的值類似是Double,因此可以簡化如下:
v.foreach((n) => println(n))

如果你只有一個引數,忽略括號
v.foreach(n => println(n))

如果你只有一個引數,還可以將節省引數,用下劃線替代:
v.foreach(println(_))

最後,如果函式方法只是呼叫一個其他方法,而且那個方法只使用一個引數,最後可以成為:
v.foreach(println)

可惜,foreach 有副作用,它不能返回任何值,大部分情況我們需要操作一個collection,並且返回值,然後,在這個返回結果上做一些操作(map/reduce),那麼使用map,注意它不是資料結構Map。

v.map(n => n * 2)

將v中每個值乘以2,然後返回結果:
Vector(2.2, 4.4, 6.6, 8.8)

簡寫如下:
v.map(_ * 2)

還有其他操作方法:

v.reverse
v.sum
v.sorted
v.min
v.max
v.size
v.isEmpty
<p class="indent">


v.permutations 是產生一個遍歷指標iterator
v.permutations.foreach(println).

另外有用功能是zip,可以把兩個集合合併在一起:
Vector(1,2,3).zip(Vector(4,5,6))
結果是:
Vector((1,4), (2,5), (3,6))

v.zip(v.map(_ * 2))結果是:
Vector((1.1,2.2), (2.2,4.4), (3.3,6.6), (4.4,8.8))

匿名函式非常簡單易用,但是如果你覺得難以理解,那麼可以使用標準函式:
def timesTwo(d: Double) = d * 2

v.zip(v.map(_ * 2))就變為:v.zip(v.map(timesTwo))

模式匹配

trait Color
case class Red(saturation: Int) extends Color
case class Green(saturation: Int) extends Color
case class Blue(saturation: Int) extends Color

def matcher(arg:Any): String = arg match {
  case "Chowder" => "Make with clams"
  case x: Int => "An Int with value " + x
  case Red(100) => "Red sat 100"
  case Green(s) => "Green sat " + s
  case c: Color => "Some Color: " + c
  case w: Any => "Whatever: " + w
  case _ => "Default, but Any captures all"
}

val v = Vector(1, "Chowder", Red(100), Green(50), Blue(0), 3.14)
v.foreach(x => println(matcher(x)))
<p class="indent">

(banq注:在Java中大概要使用visitor模式來實現,複雜些)
match語法類似Java的switch 。但是break不是必須的。

case class對於模式匹配特別有用,因為可以分解它。Any是所有物件的根class,類似Java中的primitive型別,


Actor併發模型
這個在jdon已經討論過多次,是一種Actor模型,透過非同步訊息通訊,可非同步併發。要比Java多執行緒模型併發效能更好,徹底杜絕了共享鎖。


// Bunnies.scala (Run as script: scala Bunnies.scala)
case object Hop
case object Stop

case class Bunny(id: Int) extends scala.actors.Actor {
  this ! Hop // Constructor code
  start()    // ditto
  def act() {
    loop {
      react {
        case Hop =>
          print(this + " ")
          this ! Hop
          Thread.sleep(500)
        case Stop =>
          println("Stopping " + this)
          exit()
      }
    }
  }
}

val bunnies = Range(0,10).map(new Bunny(_))
println("Press RETURN to quit")
readLine
bunnies.foreach(_ ! Stop)
<p class="indent">

(banq注:看到這段感覺很象Java的執行緒類,是不是可以改造一下呢)

act()方法是一個自動的 match方式,loop{ react{看上去怪,但是scala的關鍵,開始你能使用loop來開啟郵箱訊息的match狀態,它實際是一個協調的多工,每個任務做點事情然後放棄控制權,讓下一個任務,這種方式好處沒有共享記憶體,因此可以擴充伸縮,可以達數百萬個任務。

注意class Benny開始兩行:
this ! Hop
start()
scala不能在方法內進行物件初始化,放到方法之外,類中,!感嘆號是傳送訊息,然後呼叫start()開始訊息不斷迴圈檢查。

以上是Scala主要特徵,本人看下來感覺是簡單些,特別在瞭解了函式程式設計和Actors這兩個主要程式設計正規化和思想,我在Jdon其他帖子花過大量時間來討論,有人感覺Scala複雜,可能是因為開始被誤導?實際Scala比較簡單強大。


相關文章