自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Inside Scala:王在祥的Scala學(xué)習(xí)筆記

開(kāi)發(fā) 后端
本文是王在祥先生的Scala學(xué)習(xí)博文系列。在這個(gè)系列中,王在祥先生分篇講述了Scala中的一些有意思的語(yǔ)言特性,如curry,case類(lèi),模式匹配等等。

編者注:本系列來(lái)自王在祥先生的博客,主要是分篇總結(jié)了對(duì)于Scala一些語(yǔ)言特性的心得,為了便于與大家分享而在此轉(zhuǎn)載。這個(gè)系列適合對(duì)Scala以及函數(shù)式語(yǔ)言有一定了解的開(kāi)發(fā)者閱讀。

51CTO編輯推薦:Scala編程語(yǔ)言專(zhuān)題

Inside Scala - 1:Partially applied functions

Partially applied function(不完全應(yīng)用的函數(shù))是scala中的一種curry機(jī)制,本文將通過(guò)一個(gè)簡(jiǎn)單的實(shí)例來(lái)描述在scala中 partially applied function的內(nèi)部機(jī)制。

 

  1. // Test3.scala  
  2. package test  
  3.  
  4. object Test3 {  
  5.  
  6.   def sum(x:Int, y:Int, z:Int) = x + y + z  
  7.     
  8.   def main(args: Array[String]) {  
  9.     val sum1 = sum _  
  10.     val sum2 = sum(1, _:Int, 3)  
  11.     println(sum1(1,2,3))  
  12.     println(sum2(2))  
  13.     List(1,2,3,4).foreach(println);  
  14.     List(1,2,3,4).foreach(println _)  
  15.   }  
  16.  
  17. }  
  18.  

 

在這個(gè)代碼中 sum _ 表示了一個(gè) 新的類(lèi)型為 (Int,Int,Int)=>Int 的函數(shù),實(shí)際上,Scala 會(huì)生成一個(gè)新的匿名函數(shù)(是一個(gè)函數(shù)對(duì)象,F(xiàn)unction3),這個(gè)函數(shù)對(duì)象的apply方法會(huì)調(diào)用 sum 這個(gè)對(duì)象方法(在這里,是方法,而不是一個(gè)函數(shù))。
sum2 是一個(gè) Int => Int的函數(shù)(對(duì)象),這個(gè)函數(shù)的apply方法會(huì)調(diào)用 sum 對(duì)象方法。
后面的兩行代碼都需要訪問(wèn) println, println是在在Predef對(duì)象中定義的方法,在scala中,實(shí)際上都會(huì)生成一個(gè)臨時(shí)的函數(shù)對(duì)象,來(lái)包裝對(duì) println 方法的調(diào)用。如果研究一下scala生成的代碼,那么可以發(fā)現(xiàn),目前生成的代碼中, 對(duì) println, println _生成的代碼是重復(fù)的,這也說(shuō)明,目前,所有的你匿名函數(shù)基本上沒(méi)有進(jìn)行重復(fù)性檢查。(這可能導(dǎo)致編譯生成的的類(lèi)更大)。

從這里可以得知,雖然,在語(yǔ)法層面,方法(所有的def出來(lái)的東西)與函數(shù)看起來(lái)是一致的,但實(shí)際上,二者在底層有區(qū)別,方法仍然是不可以直接定位、傳值的,他不是一個(gè)對(duì)象。而僅僅是JVM底層可訪問(wèn)的一個(gè)實(shí)體。而函數(shù)則是虛擬機(jī)層面的一個(gè)對(duì)象。任何從方法到函數(shù)的轉(zhuǎn)換,Scala會(huì)自動(dòng)生成一個(gè)匿名的函數(shù)對(duì)象,來(lái)進(jìn)行相應(yīng)的轉(zhuǎn)換。

所以, List(1,2,3,4).foreach(println) 在底層執(zhí)行時(shí),并不是獲得了一個(gè)println的引用(實(shí)際上,根本不存在println這個(gè)可訪問(wèn)的對(duì)象),而是scala自動(dòng)產(chǎn)生一個(gè)匿名的函數(shù),這個(gè)函數(shù)會(huì)調(diào)用println。

當(dāng)然,將一個(gè)函數(shù)傳遞時(shí),Scala是不會(huì)再做不必要的包裝的,而是直接傳遞這個(gè)函數(shù)對(duì)象了。

#p#

Inside Scala - 2: Curry Functions

Curry,在函數(shù)式語(yǔ)言中是很常見(jiàn)的,在scala中,對(duì)其有特別的支持。

package test

 

  1. object TestCurry {  
  2.  
  3.   def sum(x:Int)(y:Int)(z:Int) = x + y + z  
  4.  
  5.   def main(args: Array[String]){  
  6.  
  7.     val sum1: (Int => Int => Int) = sum(1)  
  8.     val sum12: Int => Int = sum(1)(2)  
  9.     val sum123 = sum(1)(2)(3)  
  10.     println(sum1(2)(3))  
  11.     println(sum12(3))  
  12.     println(sum123)  
  13.  
  14.   }  
  15.  
  16. }  
  17.  

 

在這個(gè)例子中, sum 被設(shè)計(jì)成為一個(gè)curried函數(shù),(多級(jí)函數(shù)?),研究一個(gè)函數(shù)的實(shí)現(xiàn)是很有意思的:

如果看生成的 sum 函數(shù)代碼,那么,它與 如下編寫(xiě)的
def sum(x:Int, y:Int: z:Int) = x + y + z 是一致的。
而且,如果,你調(diào)用sum(1)(2)(3),實(shí)際上,scala也并不會(huì)產(chǎn)生3次函數(shù)調(diào)用,而是一次 sum(1,2,3)

也就是說(shuō),如果你沒(méi)有進(jìn)行 sum(1), sum(1)(2)等調(diào)用,那么實(shí)際上,上述的代碼中根本不會(huì)生成額外的函數(shù)處理代碼。但是,如果我們需要進(jìn)行一些常用的curry操作時(shí),scala為我們提供了額外的語(yǔ)法級(jí)的便利。

#p#

Inside Scala - 3: How Trait works

Scala中Trait應(yīng)該是一個(gè)非常強(qiáng)大,但又有些復(fù)雜的概念,至少與我,我對(duì)trait總是有一些不太明了的地方,求人不如求己,對(duì)這些疑問(wèn)還是自己動(dòng)手探真的比較好。

還是從一個(gè)簡(jiǎn)單的實(shí)例著手。

package test

 

  1. import java.awt.Point  
  2.  
  3. object TestTrait {  
  4.  
  5.   trait Rectangular {  
  6.     def topLeft: Point  
  7.     def bottomRight: Point  
  8.       
  9.     def left = topLeft.x  
  10.     def top = topLeft.y  
  11.     def right = bottomRight.x  
  12.     def bottom = bottomRight.y  
  13.     def width = right - left  
  14.     def height = bottom - top  
  15.   }  
  16.     
  17.   class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular {  
  18.     override def toString = "I am a rectangle" 
  19.   }  
  20.  
  21. }  
  22.  

 

對(duì)這段代碼,我想問(wèn)如下的幾個(gè)問(wèn)題:
Rectangle是如何繼承 Rectangular的行為,如 left, right, width, height的?
Rectangular 對(duì)應(yīng)于Java的接口,那么,相關(guān)的實(shí)現(xiàn)代碼又是如何保存的?
其實(shí),這兩個(gè)問(wèn)題是相關(guān)的。研究這個(gè)問(wèn)題的最直接的辦法莫過(guò)于直接分析scalac編譯后的結(jié)果。
這個(gè)類(lèi)編譯后包括:
TestTrait.class 這個(gè)類(lèi)
TestTrait$.class 其實(shí)就是 object TestTrait這個(gè)對(duì)象的類(lèi)。一個(gè)object實(shí)際上從屬于一個(gè)類(lèi),scala是對(duì)其加后綴$
在這個(gè)例子中,TestTrait這個(gè)對(duì)象實(shí)際上并未定義新的屬性和方法,因此,并沒(méi)有包含什么內(nèi)容
TestTrait$Rectangular.class
對(duì)應(yīng)于代碼中的Rectangular這個(gè)trait,這實(shí)際上是一個(gè)接口類(lèi)。對(duì)應(yīng)的就是這個(gè)trait中定義的全部方法。包括topLeft, bottomRight以及后續(xù)的實(shí)現(xiàn)方法left, width等的接口定義
 

  1. public interface test.TestTrait$Rectangular extends scala.ScalaObject{  
  2.  
  3. public abstract int height();  
  4. public abstract int width();  
  5. public abstract int bottom();  
  6. public abstract int right();  
  7. public abstract int top();  
  8. public abstract int left();  
  9. public abstract java.awt.Point bottomRight();  
  10. public abstract java.awt.Point topLeft();  
  11.  
  12. }  

TestTrait$Rectangular$class.class
這個(gè)類(lèi)實(shí)際上是trait邏輯的實(shí)現(xiàn)類(lèi)。由于JVM中,接口是不支持任何的實(shí)現(xiàn)代碼的,因此,scala將相關(guān)的邏輯代碼編譯在這個(gè)類(lèi)中

 

  1. public abstract class test.TestTrait$Rectangular$class extends java.lang.Object{  
  2.  
  3. public static void $init$(test.TestTrait$Rectangular); // 在這個(gè)例子中,沒(méi)有trait的初始化相關(guān)操作  
  4.   Code:  
  5.    0:   return 
  6.  
  7. public static int height(test.TestTrait$Rectangular);   // 對(duì)應(yīng)于height = bottom - top這個(gè)操作的實(shí)現(xiàn)  
  8.   Code:  
  9.    0:   aload_0  
  10.    1:   invokeinterface #17,  1//InterfaceMethod test/TestTrait$Rectangular.bottom:()I  
  11.    6:   aload_0  
  12.    7:   invokeinterface #20,  1//InterfaceMethod test/TestTrait$Rectangular.top:()I  
  13.    12:  isub  
  14.    13:  ireturn  
  15.  

 

更多的方法并不在此羅列。
首先,這個(gè)實(shí)現(xiàn)類(lèi)是抽象的,它不需要被實(shí)例化。
所有的trait方法,其實(shí)接收一個(gè)額外的參數(shù),即 this 對(duì)象。對(duì)對(duì)象的任何的訪問(wèn),如bottom等操作,實(shí)際上是直接調(diào)用對(duì)象的相應(yīng)操作。
所有的trait方法,都是static的。
TestTrait$Rectangle.class
這個(gè)就是Rectangle這個(gè)類(lèi)的代碼了。

 

  1. // 首先,實(shí)現(xiàn)類(lèi)以implements的方式繼承了trait所定義的接口。  
  2. public class test.TestTrait$Rectangle extends java.lang.Object implements test.TestTrait$Rectangular,scala.ScalaObject{  
  3.  
  4. // 類(lèi)的val屬性直接對(duì)應(yīng)于一個(gè)同名的private字段和相應(yīng)的讀取方法。  
  5. private final java.awt.Point bottomRight;  
  6.  
  7. private final java.awt.Point topLeft;  
  8.  
  9. // scala對(duì)象比較特殊的是,相應(yīng)字段的初始化比調(diào)用父類(lèi)構(gòu)造函數(shù)來(lái)得更早。也就是說(shuō),在Class(arg)中的參數(shù)是最早被初始化的。  
  10. // 在構(gòu)造函數(shù)后,可以看到,會(huì)調(diào)用trait的初始化代碼。當(dāng)然,在我們的這個(gè)例子中,trait沒(méi)有任何的初始化行為。  
  11. public test.TestTrait$Rectangle(java.awt.Point, java.awt.Point);  
  12.   Code:  
  13.    0:   aload_0  
  14.    1:   aload_1  
  15.    2:   putfield        #13//Field topLeft:Ljava/awt/Point;  
  16.    5:   aload_0  
  17.    6:   aload_2  
  18.    7:   putfield        #15//Field bottomRight:Ljava/awt/Point;  
  19.    10:  aload_0  
  20.    11:  invokespecial   #20//Method java/lang/Object."":()V  
  21.    14:  aload_0  
  22.    15:  invokestatic    #26//Method test/TestTrait$Rectangular$class.$init$:(Ltest/TestTrait$Rectangular;)V  
  23.    18:  return 
  24.  
  25. // height這個(gè)函數(shù)是從trait中繼承的,在這里,繼承體現(xiàn)為對(duì)trait實(shí)現(xiàn)類(lèi)的一個(gè)調(diào)用,同時(shí),將對(duì)象本身作為this傳遞給該函數(shù)  
  26. public int height();  
  27.   Code:  
  28.    0:   aload_0  
  29.    1:   invokestatic    #39//Method test/TestTrait$Rectangular$class.height:(Ltest/TestTrait$Rectangular;)I  
  30.    4:   ireturn  
  31.  

 

這里不再羅列其他的函數(shù)實(shí)現(xiàn),其基本與height函數(shù)是相一致的。

理解了以上的邏輯,trait是如何實(shí)現(xiàn)將接口和接口實(shí)現(xiàn)溶于一體的,應(yīng)該就非常的清楚了。我以前一直在納悶一個(gè)問(wèn)題:接口中不能夠包含實(shí)現(xiàn)代碼,那么,難道每次編譯繼承trait的類(lèi)時(shí),這寫(xiě)實(shí)現(xiàn)的代碼是怎么在子類(lèi)中繼承的呢?難道是編譯器將這個(gè)邏輯復(fù)制了一份?如果這樣,不僅生成的代碼量很大,而且,還有一個(gè)問(wèn)題,那就是,在編譯時(shí)需要有trait的源代碼才行。經(jīng)過(guò)上面的剖析,我們終于知道scala其實(shí)有更***的解決之道的:那就是一個(gè)trait輔助類(lèi)。

#p#

Inside Scala - 4: Trait Stacks

這個(gè)例子摘自 Programming In Scala 這本書(shū)第12.5節(jié)。本文將從另外一個(gè)角度來(lái)分析 Stackable Trait的內(nèi)部原理。

package test

 

  1. import scala.collection.mutable.ArrayBuffer  
  2.  
  3. object Test7 {  
  4.  
  5.   abstract class IntQueue {  
  6.     def put(x:Int)  
  7.     def get(): Int  
  8.   }  
  9.     
  10.   class BasicIntQueue extends IntQueue {  
  11.     private val buf = new ArrayBuffer[Int]  
  12.     def put(x:Int) { buf += x }  
  13.     def get() = buf.remove(0)  
  14.   }  
  15.  
  16.   trait Doubling extends IntQueue {  
  17.     abstract override def put(x:Int) { super.put(2*x) }  
  18.   }  
  19.  
  20.   def main(args: Array[String]) {  
  21.     val queue: IntQueue = new BasicIntQueue with Doubling  
  22.     queue.put(1)  
  23.     queue.put(5)  
  24.     println( queue.get )  
  25.     println( queue.get )  
  26.   }  
  27.  
  28. }  
  29.  

 

我們來(lái)看這一行代碼 val queue = new BasicIntQue with Doubling,Scala針對(duì)這一行代碼干了很多很多的工作,并不是一個(gè)簡(jiǎn)單的操作那么簡(jiǎn)單
Scala需要新生成一個(gè)類(lèi)型,在我的環(huán)境中,這個(gè)類(lèi)叫做:Test7$$anon$1,看看這個(gè)代碼:
// 新的類(lèi)以BasicIntQueue為父類(lèi),同時(shí)實(shí)現(xiàn)了Doubling這個(gè)trait定義的接口
 

  1. public final class test.Test7$$anon$1 extends test.Test7$BasicIntQueue implements test.Test7$Doubling{  
  2.  
  3. public test.Test7$$anon$1();  
  4.   Code:  
  5.    0:   aload_0  
  6.    1:   invokespecial   #10//Method test/Test7$BasicIntQueue."":()V // 父類(lèi)初始化  
  7.    4:   aload_0  
  8.    5:   invokestatic    #16//Method test/Test7$Doubling$class.$init$:(Ltest/Test7$Doubling;)V // trait輔助類(lèi)初始化  
  9.    8:   return 
  10.  
  11. public void put(int);  
  12.   Code:  
  13.    0:   aload_0  
  14.    1:   iload_1  
  15.    2:   invokestatic    #21//Method test/Test7$Doubling$class.put:(Ltest/Test7$Doubling;I)V // 這個(gè)類(lèi)使用的是Doubling提供的版本  
  16.    5:   return 
  17.  
  18. public final void test$Test7$Doubling$$super$put(int); // Doubling所需要的super的版本  
  19.   Code:  
  20.    0:   aload_0  
  21.    1:   iload_1  
  22.    2:   invokespecial   #29//Method test/Test7$BasicIntQueue.put:(I)V  
  23.    5:   return 
  24.  
  25. }  
  26.  

我們來(lái)分析一下Doubling這個(gè)trait的實(shí)現(xiàn)
 

  1. public interface test.Test7$Doubling extends scala.ScalaObject{  
  2.  
  3. public abstract void put(int);  // 這個(gè)是trait中實(shí)現(xiàn)的方法  
  4.  
  5. public abstract void test$Test7$Doubling$$super$put(int); // 這個(gè)是這個(gè)trait 額外依賴(lài)的方法  
  6.  
  7. }  
  8.  
  9. // Doubling這個(gè)trait的輔助類(lèi)  
  10. public abstract class test.Test7$Doubling$class extends java.lang.Object{  
  11. public static void $init$(test.Test7$Doubling);  
  12.   Code:  
  13.    0:   return 
  14.  
  15. public static void put(test.Test7$Doubling, int);  
  16.   Code:  
  17.    0:   aload_0  
  18.    1:   iconst_2  
  19.    2:   iload_1  
  20.    3:   imul  
  21.    4:   invokeinterface #17,  2//InterfaceMethod test/Test7$Doubling.test$Test7$Doubling$$super$put:(I)V  
  22. // 這也是 Doubling這個(gè)接口中需要 super.init這個(gè)方法的原因。  
  23.    9:   return 
  24.  
  25. }  
  26.  

由此可見(jiàn),編譯器在處理 val queue: IntQueue = new BasicIntQueue with Doubling這一行代碼時(shí),需要確定類(lèi)、Trait的先后順序。這也是理解Trait的最為復(fù)雜的一環(huán)。后續(xù),我將就這個(gè)問(wèn)題進(jìn)行分析。

#p#

Inside Scala - 5: Trait Stacks

繼續(xù)上一個(gè)案例,現(xiàn)在我們將Trait的鏈搞得更長(zhǎng)一些:

 

  1. trait Incrementing extends IntQueue {  
  2. abstract override def put(x: Int) { super.put(x + 1) }  
  3. }  
  4. trait Filtering extends IntQueue {  
  5. abstract override def put(x: Int) {  
  6. if (x >= 0super.put(x)  
  7. }  
  8. }  
  9.  
  10. val queue: IntQueue = new BasicIntQueue with Incrementing with Filtering  
  11.  

 

新的類(lèi)如何呢?當(dāng)我們調(diào)用 queue的 put方法時(shí),這個(gè)的先后順序究竟如何呢?還是看看生成的代碼:
 

  1. public final class test.Test7$$anon$1 extends test.Test7$BasicIntQueue implements test.Test7$Incrementing,test.Test7$Filtering{  
  2.  
  3. // 初始化的順序:先父類(lèi)、再I(mǎi)ncremeting、再Filtering,這個(gè)順序與源代碼的順序是一致的。  
  4. public test.Test7$$anon$1();  
  5.   Code:  
  6.    0:   aload_0  
  7.    1:   invokespecial   #10//Method test/Test7$BasicIntQueue."":()V  
  8.    4:   aload_0  
  9.    5:   invokestatic    #16//Method test/Test7$Incrementing$class.$init$:(Ltest/Test7$Incrementing;)V  
  10.    8:   aload_0  
  11.    9:   invokestatic    #21//Method test/Test7$Filtering$class.$init$:(Ltest/Test7$Filtering;)V  
  12.    12:  return 
  13.  
  14. // put 方法實(shí)際使用的是 Filtering這個(gè)Trait的put  
  15. public void put(int);  
  16.   Code:  
  17.    0:   aload_0  
  18.    1:   iload_1  
  19.    2:   invokestatic    #34//Method test/Test7$Filtering$class.put:(Ltest/Test7$Filtering;I)V  
  20.    5:   return 
  21.  
  22. // Filtering Trait的父實(shí)現(xiàn)是Incremeting trait  
  23. public final void test$Test7$Filtering$$super$put(int);  
  24.   Code:  
  25.    0:   aload_0  
  26.    1:   iload_1  
  27.    2:   invokestatic    #38//Method test/Test7$Incrementing$class.put:(Ltest/Test7$Incrementing;I)V  
  28.    5:   return 
  29.  
  30. // incrementing的父實(shí)現(xiàn)是父類(lèi)的實(shí)現(xiàn)。  
  31. public final void test$Test7$Incrementing$$super$put(int);  
  32.   Code:  
  33.    0:   aload_0  
  34.    1:   iload_1  
  35.    2:   invokespecial   #26//Method test/Test7$BasicIntQueue.put:(I)V  
  36.    5:   return 
  37.  
  38. }  
  39.  

 

因此,要理解這個(gè)過(guò)程,可以這么來(lái)分析:val queue: IntQueue = new BasicIntQueue with Incrementing with Filtering
首先初始化的是BasicIntQueue
在這個(gè)基礎(chǔ)上疊加 Incrementing,super.put引用的是BasicIntQueue的put方法
再在疊加后的基礎(chǔ)上疊加 Filtering,super.put引用的是 Incrementing的put方法
疊加后的結(jié)果就是***的版本。put引用的是Filtering的put方法
因此,初始化的順序是從左至右,而方法的可見(jiàn)性則是從右至左(可以理解為上面的疊加關(guān)系,疊加之后,上面的trait具有更大的優(yōu)先可見(jiàn)性。

#p#

Inside Scala - 6:Case Class 與 模式匹配

本文將嘗試對(duì)Case Class是如何參與模式匹配的進(jìn)行剖析。文中的代碼還是來(lái)自 Programming In Scala一書(shū)。

  1. abstract class Expr;  
  2. case class Var(name: String) extends Expr;  
  3. case class Number(num: Double) extends Expr;  
  4. case class UnOp(operator: String, arg: Expr) extends Expr;  
  5. case class BinOp(operator:String, left: Expr, right: Expr) extends Expr; 

這里我們先來(lái)看一個(gè)最為簡(jiǎn)單的模式匹配
 

  1. some match {  
  2.   case Var(name) => println("a var with name:" + name)  


這幾行的代碼編譯后等效于:

  1. if(some instanceof Var)  
  2. {  
  3.     Var temp21 = (Var)some;  
  4.     String name = temp21.name();  
  5.     if(true)  
  6.     {  
  7.         name = temp22;  
  8.         Predef$.MODULE$.println((new StringBuilder()).append("a var with name:").append(name).toString());  
  9.     } else 
  10.     {  
  11.         throw new MatchError(some.toString());  
  12.     }  
  13. else 
  14. {  
  15.     throw new MatchError(some.toString());  


如果從生成的代碼的角度上來(lái)看,Scala生成的代碼質(zhì)量并不高,其中的 if(true) else 的那個(gè)部分就有明顯的廢代碼。(不過(guò),這個(gè)對(duì)運(yùn)行效率的影響到時(shí)幾乎可以忽略,只是編譯后的字節(jié)碼倒是沒(méi)理由的多了幾分)。
上面的這個(gè)模式匹配僅僅是匹配一個(gè)類(lèi)型。因此,其對(duì)應(yīng)的java原語(yǔ)就是 instanceof 檢測(cè)。

讓我們更進(jìn)一步, 看看如下的例子:
 

  1. some match {  
  2.   case Var("x") => println("a var with name:x")  

這個(gè)模式匹配不僅匹配類(lèi)型,還要匹配構(gòu)造器中的name屬性為 "x"常量。這里我就不在福州 Scala生成的字節(jié)碼了,而是簡(jiǎn)單的翻譯一下:
if( some instanceof Var)  -- 類(lèi)型檢查
var.name() == "x"             -- 檢查 對(duì)象的 name 屬性是否等于 "x",編譯器非常清楚的指導(dǎo) Case Class的每一個(gè)構(gòu)造參數(shù)所對(duì)應(yīng)的字段名稱(chēng)。

更進(jìn)一步,讓我們看看一個(gè)更復(fù)雜的模式匹配:嵌套的對(duì)象。
 

  1. some match {  
  2.   case BinOp("+", Var("x"), UnOp("-", Number(num))) => println("x - " + num)  

這個(gè)邏輯其實(shí)也是上面的一個(gè)嵌套:
some instanceof BinOp
some.operator == "+"  編譯器進(jìn)行了特殊的null檢測(cè),以防止這個(gè)操作出現(xiàn)NPE
some.left instanceof Var
some.left.name == "x"
some.right instanceof UnOp
some.right.operator == "-"
some.right.arg instanceof Number
......
實(shí)際上,Scala的模式匹配確實(shí)為我們干了很多很多的事情,這也使得在很多的情況下,使用scala的模式匹配為我們提供了一個(gè)非常安全的(不用擔(dān)心大量的Null檢查),以及非常復(fù)雜的匹配操作。當(dāng)然,與更復(fù)雜的模式匹配相比(譬如,規(guī)則引擎其實(shí)也是一個(gè)模式匹配的引擎),Scala的模式匹配還是相對(duì)比較簡(jiǎn)單的。

這里簡(jiǎn)單的補(bǔ)充一下 Scala中的幾種模式:
1、通配符模式。 也就是說(shuō)使用 case _ => 來(lái)匹配所有的東西?;蛘撸琧ase Var(_) 來(lái)對(duì)局部進(jìn)行通配。
2、常量匹配。譬如上述的Var("x") ,其中,"x"就是一個(gè)常量。常量除了文字常量外,還可以使用以大寫(xiě)字母開(kāi)頭的scala變量,或者`varname`形式的引用。
3、變量匹配。一個(gè)變量匹配實(shí)際上匹配任何的類(lèi)型,并同時(shí)賦予其一個(gè)變量名。
4、構(gòu)造函數(shù)匹配。匹配一個(gè)給定的類(lèi)型,并且嵌套的對(duì)其參數(shù)進(jìn)行匹配。參數(shù)可以是通配符模式、常量、變量或者子構(gòu)造函數(shù)匹配
5、對(duì)于List類(lèi)型, _*可以匹配剩余的全部元素。
6、Tuple匹配。(a,b,c)
7、類(lèi)型匹配。對(duì)于java對(duì)象,由于并不適合Scala的Case Class模型,因此,可以使用類(lèi)型進(jìn)行匹配。在這種情況下,與構(gòu)造子匹配是不同的。

再摘一段我以前編寫(xiě)的使用scala來(lái)編寫(xiě)應(yīng)用程序的邏輯代碼,讓我們看看模式匹配在商業(yè)應(yīng)用中的使用:

  1. _req.transType match {  
  2.       case RechargeEcp | RechargeGnete | FreezeToAvailable => // 充值類(lèi)交易  
  3.         assert(_req.amount > 0"金額不正確")  
  4.       case DirectPay | AvailableToFreeze =>    // 支付、凍結(jié)類(lèi)交易  
  5.         assert(_req.amount < 0"金額不正確")  
  6.       case _ =>      
  7.         assert(false"無(wú)效交易類(lèi)型")  
  8.     }  
  9.       
  10.     val _account = queryEwAccount(_req.userId)  
  11.     assert(_account != null"用戶(hù)尚未開(kāi)通電子錢(qián)包")  
  12.       
  13.     var _accAvail, _accFreeze: EWSubAccount = null 
  14.     var _total: BigDecimal = _req.amount  
  15.     _account.subAccounts.find(_.subTypeCode==Available) match {  
  16.       case Some(x) =>  _accAvail = x;    _total += x.balance  
  17.       case None =>  
  18.     }  
  19.     _account.subAccounts.find(_.subTypeCode==Freeze) match {  
  20.       case Some(x) =>  _accFreeze = x;    _total += x.balance  
  21.       case None=>  
  22.     }  

這個(gè)僅僅是一個(gè)很簡(jiǎn)單的應(yīng)用,試想使用Java的if/else或者switch來(lái)進(jìn)行相同的代碼,你不妨看看代碼量會(huì)增加多少?可讀性又會(huì)如何呢?

#p#

Scala Actor是一種借鑒于Erlang的進(jìn)程消息機(jī)制的并發(fā)編程模式,由于Java中不存在Erlang的進(jìn)程的概念,因此,Scala的Actor在隔離性上是不如Erlang的,譬如,在Erlang中,可以有效的終止一個(gè)進(jìn)程,不僅僅無(wú)需擔(dān)心死鎖(根本沒(méi)有鎖),也可以馬上釋放掉改進(jìn)程的內(nèi)存,這種隔離性在某種程度上是更接近于操作系統(tǒng)的進(jìn)程的。在Java的世界里暫時(shí)沒(méi)有等效的替代品。

(題外話,最近在我們的Open Service Platform中集成了一個(gè)類(lèi)似于操作系統(tǒng)定時(shí)調(diào)度的機(jī)制,可以定時(shí)執(zhí)行一些任務(wù),但是***,我們?nèi)匀粵Q定將部分非交易相關(guān)的定時(shí)任務(wù),主要是一些日志分析類(lèi)、管理性批量處理等定時(shí)任務(wù)放到操作系統(tǒng)上進(jìn)行調(diào)度,畢竟操作系統(tǒng)提供了一個(gè)更好的虛擬機(jī),在OSGi層面仍然是有限的隔離,哪一天JVM能夠提供像操作系統(tǒng)的隔離特性,那么,操作系統(tǒng)就真的不重要了)。

本文將對(duì)actor的機(jī)制進(jìn)行簡(jiǎn)單的分析,以幫助加強(qiáng)對(duì)actor的理解。

  1. package learn.actor  
  2.  
  3. object Test1 extends Application {  
  4.  
  5.   import scala.actors.Actor._  
  6.  
  7.   val actor1 = actor {  
  8.  
  9.     println("i am in " + Thread.currentThread)  
  10.  
  11.     while(true) {  
  12.  
  13.       receive {  
  14.  
  15.         case msg => println("recieve msg:" + msg + " In " + Thread.currentThread);  
  16.  
  17.       }  
  18.  
  19.     }  
  20.  
  21.   }  
  22.  
  23.    
  24.  
  25.   val actor2 = actor {  
  26.  
  27.     println("i am in " + Thread.currentThread)  
  28.  
  29.     while(true) {  
  30.  
  31.       receive {  
  32.  
  33.         case msg: String => println("recieve msg:" + msg.toUpperCase + " In " + Thread.currentThread);  
  34.  
  35.       }  
  36.  
  37.     }  
  38.  
  39.   }  
  40.  
  41.    
  42.  
  43.   actor1 ! "Hello World" 
  44.  
  45.   actor2 ! "Hello World" 
  46.  
  47.   actor1 ! "ok" 
  48.  
  49.   actor2 ! "ok" 
  50.  
  51.    
  52.  
  53. }  
  54.  

運(yùn)行的結(jié)果是:

  1. i am in Thread[pool-1-thread-1,5,main]  
  2. i am in Thread[pool-1-thread-2,5,main]  
  3. recieve msg:HELLO WORLD In Thread[pool-1-thread-2,5,main]  
  4. recieve msg:Hello World In Thread[pool-1-thread-1,5,main]  
  5. recieve msg:OK In Thread[pool-1-thread-2,5,main]  
  6. recieve msg:ok In Thread[pool-1-thread-1,5,main] 

從這個(gè)例子來(lái)看,actor1和actor2實(shí)際上是兩個(gè)獨(dú)立的Java線程,任何線程可以將消息以 ! 的方式發(fā)給給這個(gè)線程進(jìn)行處理。由于采用消息的方式來(lái)進(jìn)行通信,因此,線程與線程之間無(wú)需采用Java的notify/wait機(jī)制,而后者是建立在鎖的基礎(chǔ)之上的。有關(guān)于這一點(diǎn),我不在本文只進(jìn)行深入的分析了。(有必要的話,我會(huì)再寫(xiě)一個(gè)帖子來(lái)說(shuō)明)。

那么 Scala Actor 的底層基礎(chǔ)是什么呢?與Java的notify/wait就完全沒(méi)有關(guān)系嗎?我們將重點(diǎn)分析actor的三個(gè)方法:!, receive, react

1、Scala Actor的send(外部調(diào)用者發(fā)送一個(gè)消息給當(dāng)前actor)和receive(當(dāng)前actor接收一個(gè)消息),這兩個(gè)操作是同步的(synchronized),也就是說(shuō),不可同時(shí)進(jìn)入。(客觀的說(shuō),這一塊應(yīng)該有很大的優(yōu)化空間,應(yīng)該采用樂(lè)觀鎖的機(jī)制,可能會(huì)有更好的效率,一來(lái),send/receive操作本身都是很快速的操作,即便在出現(xiàn)沖突的情況下,使用樂(lè)觀鎖也可以降低線程切換引起的開(kāi)銷(xiāo),而且,在大部分情況下,send操作與receive操作引發(fā)沖突的可能性并不是很大的。也就是說(shuō),在很大的程度上,send和receive還可以有更好的并行性,不知道后續(xù)的scala版本是否會(huì)進(jìn)行優(yōu)化。)

2、執(zhí)行send操作時(shí),如果當(dāng)前actor正在等待這個(gè)消息(指actor自身已經(jīng)在receive、react并且期待這個(gè)消息的情況下),那么原來(lái)的等待將會(huì)馬上執(zhí)行,否則,消息會(huì)進(jìn)入到actor的郵箱,等待下次receive/react的處理。這種模式相較于全部放入郵箱更加有效。它避免了一次在郵箱上的同步等待。

3、當(dāng)執(zhí)行receive操作時(shí),actor會(huì)檢查對(duì)象的郵箱,如果有匹配的消息的話,則會(huì)馬上返回該消息進(jìn)行處理,否則會(huì)處在等待狀態(tài)(當(dāng)前線程阻塞,采用的是wait原語(yǔ))當(dāng)匹配的消息到達(dá)時(shí),也是采用notify原語(yǔ)通知等待線程繼續(xù)actor的處理的。

4、react與receive不同的是,react從不返回。這個(gè)在Java的編程世界里,好像還沒(méi)有看到類(lèi)似的東西,該如何理解它呢:

react(f: ParticialFunction[Any,Unit]) 首先檢查actor的郵箱,如果有符合f的消息,則馬上提取該消息,并且在一個(gè)ExecutionPool中調(diào)度執(zhí)行f。(因此,f的執(zhí)行肯定不在請(qǐng)求react這個(gè)線程中執(zhí)行的。當(dāng)前的調(diào)用react的線程,將產(chǎn)生一個(gè) SuspendActorException,從而中斷一般的執(zhí)行過(guò)程。(也就是說(shuō)文檔中說(shuō)的不返回的概念)

如果當(dāng)前郵箱中沒(méi)有消息,react將登記一個(gè)Continuation對(duì)象,將等待的消息(一個(gè)等待給定消息的函數(shù))、獲得消息后需要繼續(xù)進(jìn)行的處理在actor中進(jìn)行登記,而后,當(dāng)前線程會(huì)產(chǎn)生一個(gè)SuspendActorException,中斷處理(從而是將當(dāng)前線程歸還到線程池)。

當(dāng)消息到達(dá)(通過(guò)send)時(shí),send將檢查等待消息的Continuation,如過(guò)匹配的話,則會(huì)在線程池中的選擇一個(gè)線程來(lái)執(zhí)行f函數(shù)。在f處理完成一個(gè)消息后,一般的,它會(huì)再次調(diào)用 react來(lái)處理下一個(gè)消息,將再次重復(fù)這個(gè)過(guò)程。

應(yīng)該說(shuō),scala的這個(gè)設(shè)計(jì)是非常精巧,也非常有效的,但這對(duì)Java開(kāi)發(fā)程序員來(lái)說(shuō),就意味著一個(gè)新的挑戰(zhàn):看上去的一個(gè)函數(shù)體,實(shí)際上其中的代碼不僅是執(zhí)行不連續(xù)的(如closure可能會(huì)延遲、重復(fù)多次的被調(diào)用),甚至可能是在不同的線程中被執(zhí)行的。

從這個(gè)概念上來(lái)看,scala的actor并不對(duì)應(yīng)于Java的線程,相反,可以理解為一個(gè)行為執(zhí)行者,是一個(gè)有上下文的非操作系統(tǒng)線程,語(yǔ)義其實(shí)更接近于現(xiàn)實(shí)的一個(gè)載體。這個(gè)與Erlang的進(jìn)程還是有很明顯的語(yǔ)義上的區(qū)別的。從上述的分析中,或許如果切換到樂(lè)觀鎖的機(jī)制,Scala的并發(fā)效率還能有更進(jìn)一步的提升。

 

 

 

責(zé)任編輯:yangsai 來(lái)源: Wang Zai Xiang
相關(guān)推薦

2009-07-21 16:58:31

Scala變量范圍

2009-07-22 07:43:00

Scala閉包

2009-07-08 12:43:59

Scala ServlScala語(yǔ)言

2010-09-14 15:34:41

Scala

2009-07-08 15:35:18

Case類(lèi)Scala

2009-07-22 07:57:00

ScalaCurry化函數(shù)

2020-10-31 17:33:18

Scala語(yǔ)言函數(shù)

2009-07-22 07:47:00

Scala客戶(hù)代碼

2009-07-22 09:22:20

Scala工廠對(duì)象

2009-08-03 11:07:18

Scala Actor

2009-08-21 16:17:25

ScalaTwitter API

2009-07-22 08:57:49

Scalafinal

2009-07-22 09:02:45

Scala組合繼承

2009-09-28 11:01:39

從Java走進(jìn)Scal

2009-10-19 11:26:08

Scala循環(huán)數(shù)組

2009-07-20 16:56:51

Scala類(lèi)的定義

2009-12-09 09:15:47

從Java走進(jìn)ScalTwitter API

2009-07-22 07:53:00

Scala無(wú)參數(shù)方法

2009-06-16 17:54:38

Scala類(lèi)語(yǔ)法語(yǔ)義

2009-07-22 07:50:00

Scala傳名參數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)