在正式進入scalaz討論前我們需要理順一些基礎的scalaz結構組成概念和技巧。scalaz是由即興多態(ad-hoc polymorphism)類型(typeclass)組成。scalaz typeclass在scala中的應用有賴於scala compiler的一項特別功能:隱式轉換(implicit conversion),使程序表述更精簡。由於隱式轉換是一項compiler功能,在程序編譯(compile)的時候是由compiler來進行類型轉換代碼的產生和替代的。
讓我們先了解一下作用域(scope)和綁定(binding)。這兩樣都是在編譯程序時compiler需要解決的問題。所謂作用域解析(scope resolution)就是要確定一個綁定在一個作用域里是可視的,否則程序無法通過編譯。
作用域就是一個綁定在一個程序范圍內的可視型。作用域可以是某個類的內部或者是某個方法或函數的內部,基本上用{}就可以創造一個新的作用域了。在scala作用域可以是多層的,一個域可以存在於另一個作用域內。外部域的綁定在內部域內是可視的,反之則不然:
1 class Foo(x: Int) { 2 def temp = { 3 val y = x + 1 //x是本地域外的一個綁定
4 } 5 }
在以上的例子里x在temp{}內是可視的。一個作用域內的綁定可以屏蔽(shadow)外域定義的綁定:
1 class Foo(x: Int) { 2 def temp = { 3 val x = 0 //本地域綁定。屏蔽了外域的x
4 val y = x + 1 //y=1,x是本地域的一個綁定
5 } 6 }
綁定屏蔽是分優先次序如下:
1、本地聲明、定義或者透過繼承又或者在同一源代碼文件內的package定義的綁定最優先
2、明式申明的import如:import obj.Foo 所定義的綁定次優先
3、通配符式的import如:import obj._ 所定義的綁定再次之
4、同一package但處於不同源代碼文件內的綁定最次優先
我們用個例子來示范scope binding的優先順序:
1 package test; 2
3 // This object contains the bindings/scope tests
4 object Test { 5
6 def main(arg : Array[String]) : Unit = { 7 testSamePackage() 8 testWildcardImport() 9 testExplicitImport() 10 testInlineDefinition() 11 } 12
13 // This looks for a binding 'x' within the same package (test) as this scope.
14 def testSamePackage() { 15 println(x) // 在另外文件的test package. prints: Externally bound x object in package test
16 } 17
18 // This defines a new scope with an 'x' binding that we can import with a wildcard.
19 object Wildcard { 20 def x = "Wildcard Import x"
21 } 22
23 // This function will print the value in the binding 'x' after importing from the Wildcard object 24 // using a wildcard import.
25 def testWildcardImport() { 26 import Wildcard._ 27 println(x) // prints: Wildcard Import x
28 } 29
30 // This defines another binding of 'x' that we can import explicitly.
31 object Explicit { 32 def x = "Explicit Import x"
33 } 34
35 def testExplicitImport() { 36 import Explicit.x 37 import Wildcard._ 38 println(x) // .x優先於._ prints: Explicit Import x
39 } 40
41 // This defines an inline binding for x. Note that with all the imports, there are no ambiguous naming conflicts.
42 def testInlineDefinition() { 43 val x = "Inline definition x" //即使寫在最前,本地binding x還是最優先
44 import Explicit.x 45 import Wildcard._ 46 println(x) // prints: Inline definition x
47 } 48 }
scala compiler 在編譯程序時會根據情況自動進行隱式轉換,即代碼替代。在兩種情況下scala會進行隱形轉換:
1、在期待一個類型的地方發現了另外一個類型:
1 package learn.scalaz 2 object ab { 3 class A 4 class B 5 implicit def bToA(x: B): A = new A 6 } 7 object testApp extends App { 8 import ab._ 9 val a: A = new B //需要進行B => A的隱式轉換
10 }
在這里由於A類和B類沒有任何繼承關系,應該無法通過編譯,但scala compiler會首先嘗試搜尋B=>A的隱式轉換實例,當找到bToA函數時compiler會把new B替代成bToA(new B),如此這般才能通過編譯。
2、當一個類型並不支持某個方法時:
1 package learn.scalaz 2 object ab { 3 class A { 4 def printA = println("I am A") 5 } 6 class B 7 implicit def bToA(x: B): A = new A 8 } 9 object testApp extends App { 10 import ab._ 11 (new B).printA //需要進行B => A的隱式轉換
12 }
scala compiler 在隱式轉換中的隱式解析(implicit resolution)會用以下的策略來查找標示為implicit的實例:
1、能用作用域解析的不帶前綴的隱式綁定即:如Bar,而Foo.Bar則不符合要求
這個在以上的例子里已經示范證明了。
2、如果以上方式無法解析隱式轉換的話compiler會搜尋目標類型的隱式作用域(implicit scope)內任何對象中的隱式轉換。一個類型的隱式作用域(implicit scope)包括了涉及這個類型的所有伴生模塊(companion module)內定義的隱式轉換。例如:
def foo(implicit p: Foo),這個方法的參數必須是Foo類型。如果compiler無法進行作用域解析的話就必須搜尋隱式作用域內的匹配隱式轉換。比如Foo的伴生對象(companion object),如下:
1 object demo { 2 object Container { 3 trait Foo 4 object Foo { 5 implicit def x = new Foo { 6 override def toString = "implicit x"
7 } 8 } 9 } 10 import Container._ 11 def foo(implicit p: Foo) = println(p) //> foo: (implicit p: scalaz.learn.ex1.Container.Foo)Unit
12 foo //> implicit x
compiler在object Foo內找到了匹配的隱式轉換,程序通過了編譯。
由於compiler會首先進行作用域解析,失敗后才搜尋隱式轉換作用域,所以我們可以把一些默認隱式轉換放到隱式作用域里。然后其它編程人員可以通過import來覆載(override)使用他們自己的隱式轉換。
綜合以上所述:一個類型T的隱式作用域就是組成這個類型的所有類的伴生對象(companion object)。也就是說,T的形成有可能涉及到一組類型。在進行隱式轉換解析過程中,compiler會搜尋這些類型的伴生對象。類型T的組成部分如下:
1、所有類型T的父類:
1 object demo { 2 object Container { 3 trait A 4 trait B 5 class T extends A with B 6 object A { 7 implicit def x = new T { 8 override def toString = "implicit x"
9 } 10 } 11 } 12 import Container._ 13 def foo(implicit p: T) = println(p) //> foo: (implicit p: scalaz.learn.demo.Container.Foo)Unit
14 foo //> implicit x
類型T由A,B組成。compiler從A的伴生對象中解析到隱式轉換。
2、如果T是參數化類型,那么所有類型參數的組成類型及包嵌類的組成類型的伴生對象都在隱式轉換解析域中。如在解析List[String]中,所有List和String的伴生對象都在解析域中:
1 object demo { 2 object Container { 3 trait A 4 trait B 5 class T[A] 6 object A { 7 implicit def x = new T[A] { 8 override def toString = "implicit x"
9 } 10 } 11 } 12 import Container._ 13 def foo(implicit p: T[A]) = println(p) //> foo: (implicit p: scalaz.learn.demo.Container.T[scalaz.learn.demo.Container. 14 //| A])Unit
15 foo //> implicit x
A是T[A]的類型參數。compiler從A的伴生對象中解析到隱式轉換。
3、如果T是個單例對象(singleton object),那么T的包嵌對象(container object)就是解析域:
1 object demo { 2 object Container { 3 object T { 4 def x = "singleton object T"
5 } 6 implicit def x = T 7 } 8 import Container._ 9 def foo(implicit p: T.type) = println(p.x) //> foo: (implicit p: scalaz.learn.demo.Container.T.type)Unit
10 foo //> singleton object T
單例對象T定義於包嵌對象Container內。compiler從Container中解析到隱式轉換。
這是一篇隱式轉換解析原理的討論,不會對scala有關隱式轉換語法和調用做任何解說,希望讀者體諒。