??經過一段時間的摸索,用scala進行函數式編程的過程對我來說就好像是想著法兒如何將函數的款式對齊以及如何正確地匹配類型,真正是一種全新的體驗,但好像有點太偏重學術型了。 本來不想花什么功夫在scala的類型系統上,但在閱讀scalaz源代碼時往往遇到類型層面的編程(type level programming),常常擾亂了理解scalaz代碼思路,所以還是要簡單的介紹一下scala類型系統的一些情況。scala類型系統在scala語言教材中一般都提及到了。但有些特殊的類型如phantom type, dependent type等,以及在一些場合下使用類型的特殊技巧還是值得研究的。scala類型系統的主要功能就是在程序運行之前,在編譯時(compile time)盡量捕捉代碼中可能出現的錯誤,也就是類型不匹配錯誤。scala類型系統是通過找尋隱式轉換類型證例(implicit type evidence)來判斷代碼中當前類型是否期待的類型從而確定是否發生類型錯誤(type error)。寫起來很簡單,我們只要用隱式參數(implicit parameter)來表述一個隱式的類型實例(implicit type instance):
1 trait Proof
2 def sayHi(implicit isthere: Proof) = println("hello")
3 sayHi //編譯失敗
創建一個Proof實例后:
1 trait Proof
2 def sayHi(implicit isthere: Proof) = println("hello")
3 //> sayHi: (implicit isthere: Exercises.deptype.Proof)Unit
4 implicit object ishere extends Proof //建一個實例
5 sayHi
sayHi現在能正常通過編譯了。雖然在sayHi函數內部并沒有引用這個隱式參數isthere,但這個例子可以說明編譯器進行類型推斷的原理。一般來說我們都會在函數內部引用isthere這種隱式參數,并且按不同需要在隱式轉換解析域內創建不同功能的類型實例(instance):
1 trait Proof { def apply(): String}
2 def sayHi(implicit isthere: Proof) = println(isthere())
3 //> sayHi: (implicit isthere: Exercises.deptype.Proof)Unit
4 implicit object ishere extends Proof {def apply() = "Hello World!"}
5 sayHi //> Hello World!
在Scalaz中還有些更復雜的引用例子如:scalaz/BindSyntax.scala
def join[B](implicit ev: A <~< F[B]): F[B] = F.bind(self)(ev(_))
1 List(List(1),List(2),List(3)).join //> res0: List[Int] = List(1, 2, 3)
2 //List(1.some,2.some,3.some).join //無法編譯,輸入類型不對
以上例子里的隱式轉換和解析域就比較隱晦了:scalaz/Liskov.Scala trait LiskovFunctions {import Liskov._/**Lift Scala's subtyping relationship */implicit def isa[A, B >: A]: A <~< B = new (A <~< B) {def subst[F[-_]](p: F[B]): F[A] = p}
這個隱式轉換產生的實例限定了A必須是B或者是B的子類。在這個例子中不但限定了類型的正確性,而且還進行了些類型關系的推導。
理論上我們可以用依賴類型(dependent type)來描述類型參數之間的關系,推導結果類型最終確定代碼中類型的正確無誤。據我所知scala并不支持完整功能的依賴類型,但有些前輩在scala類型編程(type level programming)中使用了一些依賴類型的功能和技巧。Scalaz的unapply就利用了依賴類型的原理,然后通過隱式參數(implicit parameter)證明某些類型實例的存在來判斷輸入參數類型正確性的。Unapply的構思是由Miles Sabin創造的。我們先用他舉的一個例子來看看如何利用依賴類型及類型實例通過隱式輸入參數類型來推導結果類型并判斷輸入參數類型正確性的: 1 trait TypeA2 trait TypeB3 4 trait DepType[A,B,C] //依賴類型5 implicit object abb extends DepType[TypeA,TypeB,TypeB] {6 def apply(a:TypeA, b:TypeB): TypeB = error("TODO") //結果類型依賴TypeA和TypeB7 }8 implicit object aaa extends DepType[TypeA,TypeA,TypeA] {9 def apply(a:TypeA, b:TypeA): TypeA = error("TODO") //結果類型依賴TypeA和TypeA
10 }
11 implicit object iab extends DepType[Int,TypeA,TypeB] {
12 def apply(a:Int, b:TypeA): TypeB = error("TODO") //結果類型依賴Int和TypeB
13 }
14 implicit object bbi extends DepType[TypeB, TypeB, Int] {
15 def apply(a:TypeB, b:TypeB): Int = error("TODO") //結果類型依賴Int和TypeB
16 }
17 implicitly[DepType[Int,TypeA,TypeB]] //> res1: Exercises.deptype.DepType[Int,Exercises.deptype.TypeA,Exercises.deptyp
18 //| e.TypeB] = Exercises.deptype$$anonfun$main$1$iab$2$@7722c3c3
19 implicitly[DepType[TypeB,TypeB,Int]] //> res2: Exercises.deptype.DepType[Exercises.deptype.TypeB,Exercises.deptype.Ty
20 //| peB,Int] = Exercises.deptype$$anonfun$main$1$bbi$2$@2ef3eef9
21
22 implicitly[DepType[TypeA,TypeB,TypeB]] //> res3: Exercises.deptype.DepType[Exercises.deptype.TypeA,Exercises.deptype.T
23 //| ypeB,Exercises.deptype.TypeB] = Exercises.deptype$$anonfun$main$1$abb$2$@24
24 //| 3c4f91
25 implicitly[DepType[TypeA,TypeA,TypeA]] //> res4: Exercises.deptype.DepType[Exercises.deptype.TypeA,Exercises.deptype.T
26 //| ypeA,Exercises.deptype.TypeA] = Exercises.deptype$$anonfun$main$1$aaa$2$@29
27 //| 1ae
28 //implicitly[DepType[TypeA,TypeA,TypeB]] //無法通過編譯 could not find implicit value for parameter e: Exercises.deptype.DepType[Exercises.deptype.TypeA,Exercises.deptype.TypeA,Exercises.deptype.TypeB]
29
30 def checkABC[A,B,C](a: A, b: B)(implicit instance: DepType[A,B,C]): C = error("TODO")
31 //> checkABC: [A, B, C](a: A, b: B)(implicit instance: Exercises.deptype.DepTyp
32 //| e[A,B,C])C
33 /*
34 val v_aaa: TypeA = checkABC(new TypeA{},new TypeA{})
35 val v_iab: TypeB = checkABC(1,new TypeA{})
36 val v_bbi: Int = checkABC(new TypeB{},new TypeB{})
37 val v_aab: TypeB = checkABC(new TypeA{}, new TypeA{}) //ype mismatch; found : Exercises.deptype.TypeA required: Exercises.deptype.TypeB
38 */
以上例子利用依賴類型的類型關系實現了類型推導和驗證。
函數式編程重視概括抽象以方便函數組合從而實現高度的代碼重復使用。因為我們在進行函數式編程時最常遇到的類型款式是這樣的:F[A],所以我們在設計函數時會盡量對函數的參數進行針對F[A]的概括。但這樣也會對函數的使用者提出了苛刻要求:在調用函數時必須按照要求傳人F[A]類型的參數,實際上又限制了函數的通用。Scalaz里的Unapply類型可以把許多不同款式的類型對應成抽離的F[],A和TC。其中TC是個typeclass,用來引導編譯器進行類型推導。Unapply trait 如下:scalaz/Unapply.scala
trait Unapply[TC[_[_]], MA] {/** The type constructor */type M[_]/** The type that `M` was applied to */type A/** The instance of the type class */def TC: TC[M]/** Evidence that MA =:= M[A] */def leibniz: MA === M[A]/** Compatibility. */@inline final def apply(ma: MA): M[A] = leibniz(ma)
}
從定義上分析:Unapply把MA拆分出M[]和A,但使用者必須提供TC - 一個施用在A的typeclass。
好了,我們先用一個簡單的例子來分析使用Unapply的背景和具體方式:
1 class TypeWithMap[F[_],A](fa: F[A])(implicit F: Functor[F]) {
2 def doMap[B](f: A => B) = F.map(fa)(f)
3 }
4
5 val mapList = new TypeWithMap(List(1,2,3)) //> mapList : Exercises.unapply.TypeWithMap[List,Int] = Exercises.unapply$$anon
6 //| fun$main$1$TypeWithMap$1@1d9b7cce
7 mapList.doMap {_ + 1} //> res2: List[Int] = List(2, 3, 4)
在這個例子里我們通過傳入一個F[A]類型來創建一個TypeWithMap類型實例, F是個Functor。如果我們傳入一個List, 因為List的類型款式是F[A]的,所以編譯器順利地把F[A]拆解成F[_]和A, 在例子里就是List和Int。那么如果我們試著傳入一個Function1[Int,Int]呢?
1 val mapFunc = new TypeWithMap( (_: Int) * 2 )
2 //- not enough arguments for constructor TypeWithMap: (implicit F: scalaz.Functor[Any])Exercises.unapply.TypeWithMap[Any,A]. Unspecified value parameter F.
3 //- could not find implicit value for parameter F: scalaz.Functor[Any]
這個東西根本過不了編譯。主要是編譯器不曉得如何把Function1[A,A]對應成F[A]。我們試試手工把類型款式對應關系提供給編譯器:
1 val mapFunc2 = new TypeWithMap[({type l[x] = Function1[Int,x]})#l,Int]((_: Int) * 2)
2 //> mapFunc2 : Exercises.unapply.TypeWithMap[[x]Int => x,Int] = Exercises.unapp
3 //| ly$$anonfun$main$1$TypeWithMap$1@15ff3e9e
4 mapFunc2.doMap {_ + 1}(2) //> res3: Int = 5
看來沒問題,不過手工寫的還是有點復雜。Unapply是通過提供多種款式的類型隱式轉換實例(implicit instance)來進行類型匹配再分拆的。在上面的例子里Unapply提供了這么個款式的類型實例:
/**Unpack a value of type `M0[A0, B0]` into types `[b]M0[A0, b]` and `B`, given an instance of `TC` */implicit def unapplyMAB2[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[A0, α]})#λ]): Unapply[TC, M0[A0, B0]] {type M[X] = M0[A0, X]type A = B0} = new Unapply[TC, M0[A0, B0]] {type M[X] = M0[A0, X]type A = B0def TC = TC0def leibniz = refl}
這不就是我們例子的類型款式嘛。那我們看用Unapply能不能免去手工提供類型提示:
1 class TypeWithMap[F[_],A](fa: F[A])(implicit F: Functor[F]) {2 def doMap[B](f: A => B) = F.map(fa)(f)3 }4 object TypeWithMap {5 def apply[T](t: T)(implicit U: Unapply[Functor, T]) =6 new TypeWithMap[U.M,U.A](U.apply(t))(U.TC)7 }8 val umapList = TypeWithMap(List(1,2,3)) //> umapList : Exercises.unapply.TypeWithMap[[X]List[X],Int] = Exercises.unappl9 //| y$$anonfun$main$1$TypeWithMap$2@42e99e4a
10 umapList.doMap {_ + 1} //> res2: List[Int] = List(2, 3, 4)
11 val umapFunc = TypeWithMap((_: Int) * 2) //> umapFunc : Exercises.unapply.TypeWithMap[[X]Int => X,Int] = Exercises.unapp
12 //| ly$$anonfun$main$1$TypeWithMap$2@32eff876
13 umapFunc.doMap {_ + 1}(2) //> res3: Int = 5
看,不用我們提示編譯器,但我們必須提供TC的類型,在這里是Functor。注意:這里我們是對任意類型T進行分拆的。實際上U.apply(t)把T轉換成了U.M[U.A],看看Unapply的這段源代碼:
/** Evidence that MA =:= M[A] */def leibniz: MA === M[A]/** Compatibility. */@inline final def apply(ma: MA): M[A] = leibniz(ma)
從這里實現了MA >>> M[A]的轉換。 當我看到用Unapply使Int這樣的簡單類型也能轉換成M[A]時覺得挺新鮮。看看traverse操作:
1 Applicative[Option].traverse(List(1,2,3))(a => (a + 1).some)
2 //> res6: Option[List[Int]] = Some(List(2, 3, 4))
traverse函數的款式是這樣的:
final def traverse[G[_], B](f: A => G[B])(implicit G: Applicative[G]): G[F[B]]
G是個Applicative,它的類型款式當然是G[B]這樣的了,也就是我們必須提供f: A => G[B]這樣的函數款式。但如何解釋以下這句:
1 Monoid[Int].applicative.traverse(List(1,2,3))(a => a + 1)
2 //> res7: Int = 9
也就是說scalaz在什么地方把基本類型Int轉換成了G[B]這么個款式。從Unapply源代碼里查了一下,找到了這段:
sealed trait Unapply_4 {// /** Unpack a value of type `A0` into type `[a]A0`, given a instance of `TC` */implicit def unapplyA[TC[_[_]], A0](implicit TC0: TC[({type λ[α] = A0})#λ]): Unapply[TC, A0] {type M[X] = A0type A = A0} = new Unapply[TC, A0] {type M[X] = A0type A = A0def TC = TC0def leibniz = refl}
}
這就解釋了上面的可能。當然在Unapply.scala幾百行的源代碼中提供了大量不同類型款式的隱式轉換實例,大家可以在有需要的時候查找合適的分拆實例。下面我們再分析一個稍微復雜點的例子:假如我們想寫個針對List的sequence操作函數:
1 def sequenceList[G[_], A](lga: List[G[A]])(implicit G: Applicative[G]): G[List[A]] =2 lga.foldRight(List[A]().point[G])((a,b) => G.apply2(a,b){_ :: _})3 //> sequenceList: [G#7905958[_#7912581], A#7905959](lga#7912582: List#3051[G#794 //| 05958[A#7905959]])(implicit G#7912583: scalaz#31.Applicative#28655[G#7905955 //| 8])G#7905958[List#3051[A#7905959]]6 val lli = List(List(1),List(2,3),List(4)) //> lli : List#8636[List#8636[Int#1125]] = List(List(1), List(2, 3), List(4))7 val los = List("a".some,"b".some,"c".some) //> los : List#8636[Option#1959[String#248]] = List(Some(a), Some(b), Some(c))8 //| 9 sequenceList(lli) //> res6: List#8636[List#3051[Int#1125]] = List(List(1, 2, 4), List(1, 3, 4))
10 sequenceList(los) //> res7: Option#1959[List#3051[String#248]] = Some(List(a, b, c))
11
?這個sequenceList函數對任何List[G[A]]這種傳入的類型款式都可以處理。但如果出現這樣的東西呢?
1 val lether = List(1.right[String],2.right[String],3.right[String])
2 sequenceList(lether) //....required: List#3051[?G[?A]]
過不了編譯。看這個錯誤提示[?G[?A]],實際上編譯器期待的是個F[G[A]]款式的輸入參數但我們提供的是個F[G[A,B]]這么個款式,把編譯器搞糊涂了。我們試著給它點提示:
1 val lether = List(1.right[String],2.right[String],3.right[String])
2 //> lether : List#8636[scalaz#31.\/#32660[String#17383,Int#1125]] = List(\/-(1
3 //| ), \/-(2), \/-(3))
4 //sequenceList(lether) //....required: List#3051[?G[?A]]
5 sequenceList[({type l[x] = \/[String,x]})#l,Int](lether)
6 //> res8: scalaz#31.\/#32660[String#248,List#3051[Int#1125]] = \/-(List(1, 2, 3
7 //| ))
這樣就可以了。那么在Unapply里有沒有適合的款式呢?看看:
/**Unpack a value of type `M0[A0, B0]` into types `[a]M0[a, B0]` and `A`, given an instance of `TC` */implicit def unapplyMAB1[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[α, B0]})#λ]): Unapply[TC, M0[A0, B0]] {type M[X] = M0[X, B0]type A = A0} = new Unapply[TC, M0[A0, B0]] {type M[X] = M0[X, B0]type A = A0def TC = TC0def leibniz = refl}/**Unpack a value of type `M0[A0, B0]` into types `[b]M0[A0, b]` and `B`, given an instance of `TC` */implicit def unapplyMAB2[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[A0, α]})#λ]): Unapply[TC, M0[A0, B0]] {type M[X] = M0[A0, X]type A = B0} = new Unapply[TC, M0[A0, B0]] {type M[X] = M0[A0, X]type A = B0def TC = TC0def leibniz = refl}
?好像unapplMFAB1,unapplMFAB2這兩個實例都行。試試:
1 //val u1 = Unapply.unapplyMAB1[Applicative, \/, String, Int] //這個不行
2 //could not find implicit value for parameter TC0: scalaz#31.Applicative#28655[[α#75838]scalaz#31.\/#32660[α#75838,Int#1125]]
3 val u2 = Unapply.unapplyMAB2[Applicative, \/, String, Int] //這個可以
4 //> u2 : scalaz#31.Unapply#32894[scalaz#31.Applicative#28655,scalaz#31.\/#3266
5 //| 0[String#17383,Int#1125]]{type M#9842257[X#9842258] = scalaz#31.\/#32660[St
6 //| ring#17383,X#9842258]; type A#9842259 = Int#1125} = scalaz.Unapply_0$$anon$
7 //| 13@47eaca72
8 sequenceList[u2.M,u2.A](lether) //> res9: Exercises#29.unapply#17810.u2#9836539.M#9842257[List#3051[Exercises#2
9 //| 9.unapply#17810.u2#9836539.A#9842259]] = \/-(List(1, 2, 3))
不過需要我們人工判定那個款式才合適。我們可以充分利用Unapply來編寫一個更概括的sequenceList函數:
1 def sequenceListU[GA](lga: List[GA])(implicit U: Unapply[Applicative, GA]): U.M[List[U.A]] =2 sequenceList[U.M,U.A](U.leibniz.subst(lga))(U.TC)3 //> sequenceListU: [GA#10927512](lga#10936796: List#3051[GA#10927512])(implicit4 //| U#10936797: scalaz#31.Unapply#32894[scalaz#31.Applicative#28655,GA#10927515 //| 2])U#10936797.M#65840[List#3051[U#10936797.A#65842]]6 sequenceListU(lli) //> res10: List#8636[List#8636[Int#1125]] = List(List(1, 2, 4), List(1, 3, 4))7 sequenceListU(los) //> res11: Option#1959[List#8636[String#248]] = Some(List(a, b, c))8 sequenceListU(lether) //> res12: scalaz#31.\/#32660[String#248,List#8636[Int#1125]] = \/-(List(1, 2, 9 //| 3))
10 sequenceListU(List(1,2,3)) //> res13: Int#1125 = 6
這個函數夠概括的了。主要是通過leibeniz.subst把List[GA]轉換成List[G[A]], 我們看看subst的源代碼:
sealed abstract class Leibniz[-L, +H >: L, A >: L <: H, B >: L <: H] {def apply(a: A): B = subst[Id](a)def subst[F[_ >: L <: H]](p: F[A]): F[B]
...
不要慌,注意下面這兩段代碼:
/** Evidence that MA =:= M[A] */def leibniz: MA === M[A]implicit def subst[A, B](a: A)(implicit f: A === B): B = f.subst[Id](a)
leibniz返回 MA === M[A], ?subst 傳入 A 返回 B。A >>>GA, B>>>G[A]。這樣上面例子中的U.leibniz.subst(lga)就把List[GA]轉換成了List[G[A]]。
總結
以上是生活随笔 為你收集整理的Scalaz(27)- Inference Unapply :类型的推导和匹配 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。