Scalaz(27)- Inference & Unapply :类型的推导和匹配

  经过一段时间的摸索,用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 TypeA
 2 trait TypeB
 3
 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和TypeB
 7 }
 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 = B0
    def TC = TC0
    def 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.unappl
 9                                                   //| 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] = A0
    type A = A0
  } = new Unapply[TC, A0] {
    type M[X] = A0
    type A = A0
    def TC = TC0
    def 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#79
 4                                                   //| 05958[A#7905959]])(implicit G#7912583: scalaz#31.Applicative#28655[G#790595
 5                                                   //| 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 = A0
    def TC = TC0
    def 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 = B0
    def TC = TC0
    def 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])(implicit
 4                                                   //|  U#10936797: scalaz#31.Unapply#32894[scalaz#31.Applicative#28655,GA#1092751
 5                                                   //| 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]]。

时间: 2024-09-13 00:52:56

Scalaz(27)- Inference & Unapply :类型的推导和匹配的相关文章

《从零开始学Swift》学习笔记(Day 27)——可选类型

原创文章,欢迎转载.转载请注明:关东升的博客   可选类型: 我们先看看如下代码: var n1: Int = 10 n1 = nil //编译错误 let str: String = nil //编译错误 Int和String类型不能接受nil的,但程序运行过程中有时被复制给nil是在所难免的,Swift为每一种数据类型提供一种可选类型(optional),即在某个数据类型后面加上问号(?)或感叹号(!),修改前文示例代码: var n1: Int? = 10 n1 = nil let str

Android 中文件类型与MIME的匹配表

原文:http://blog.csdn.net/bigapple88/article/details/6555868 背景介绍: MIME:全称Multipurpose Internet Mail Extensions,多功能Internet 邮件扩充服务.它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但后来也应用到浏览器.MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开.多用于指定一些客户端

Scalaz(21)-类型例证:Liskov and Leibniz - type evidence

  Leskov,Leibniz,别扭的名字,是什么地干活?碰巧从scalaz源代码里发现了这么个东西:scalaz/BindSyntax.scala /** Wraps a value `self` and provides methods related to `Bind` */ final class BindOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Bind[F]) extends Ops[F[A]] { //

《圣殿祭司的ASP.NET4.0专家技术手册》---- 2-7 隐含类型局部变量及数组声明

2-7 隐含类型局部变量及数组声明 圣殿祭司的ASP.NET4.0专家技术手册以var声明的类型,叫做"隐含类型声明",像传统JavaScrip一样以var声明变量或对象. 2-7-1 初探var隐含类型声明 以下用var隐含类型声明数值及字符串的方式: var age=30 ; //声明年龄为30,为number数值型态 var name="Kevin"; //声明名字为Kevin,为string字符串型态 var隐含类型声明创造的最大目的是,为了配合LINQ查询

Scalaz(8)- typeclass:Monoid and Foldable

 Monoid是种最简单的typeclass类型.我们先看看scalaz的Monoid typeclass定义:scalaz/Monoid.scala 1 trait Monoid[F] extends Semigroup[F] { self => 2 //// 3 /** The identity element for `append`. */ 4 def zero: F 5 ... Monoid trait又继承了Semigroup:scalaz/Semigroup.scala 1 tra

Scalaz(11)- Monad:你存在的意义

 前面提到了scalaz是个函数式编程(FP)工具库.它提供了许多新的数据类型.拓展的标准类型及完整的一套typeclass来支持scala语言的函数式编程模式.我们知道:对于任何类型,我们只需要实现这个类型的typeclass实例就可以在对这个类型施用所对应typeclass提供的所有组件函数了(combinator).突然之间我们的焦点好像都放在了如何获取typeclass实例上了,从而忽略了考虑为什么要使用这些typeclass及使用什么样的typeclass这些问题了.所以可能有人会问我

Scalaz(3)- 基础篇:函数概括化-Generalizing Functions

  Scalaz是个通用的函数式编程组件库.它提供的类型.函数组件都必须具有高度的概括性才能同时支持不同数据类型的操作.可以说,scalaz提供了一整套所有编程人员都需要的具有高度概括性的通用函数,它是通过随意多态(ad-hoc polymorphism)来帮助用户使用这些函数的.随意多态就是trait+implicit parameters+implicit conversions.简单的说就是scalaz提供一个概括化的函数,用户可以在各种类型上施用这个同一函数.概括化(generalizi

Redis 介绍2——常见基本类型

Redis 的常用的数据类型 是Keys类型,string 类型,list类型,Set类型,SortedSet类型,Hash类型 1. keys redis本质上一个key-value db,所以我们首先来看看他的key.首先key也是字符串类型,但是key中不能包括边界字符 由于key不是binary safe的字符串,所以像"my key"和"mykey\n"这样包含空格和换行的key是不允许的 顺便说一下在redis内部并不限制使用binary字符,这是red

Scala入门到精通——第二十四节 高级类型 (三)

作者:摆摆少年梦 视频地址:http://blog.csdn.net/wsscy2004/article/details/38440247 本节主要内容 Type Specialization Manifest.TypeTag.ClassTag Scala类型系统总结 在scala中,类(class)与类型(type)是两个不一样的概念.我们知道类是对同一类型数据的抽象,而类型则更具体.比如定义class List[T] {}, 可以有List[Int] 和 List[String]等具体类型,