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                                             //> hello

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]]。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小二的折腾日记

LeetCode-52-N-Queens-II

只返回N皇后问题结果的种数。 因此不需要每一个字符串置位了,只需要判断一个位置的横竖,斜45度和斜135度方向的值即可。依然采用递归的方式,这里需要注意的是,由...

351
来自专栏take time, save time

你所能用到的数据结构(五)

七、骚年,这就是你的终极速度了吗? 在介绍了前面的几个排序算法之后,这一次我准备写写快速排序,快速排序之所以叫快速排序是因为它很快,它是已知实践中最快的排序算...

2675
来自专栏编程

C语言/C加加新手入门学习经验资料分享,基础知识大汇总!

C语言是面向过程的,而C++是面向对象的 相信这么努力的你 已经置顶了我 学习C语言始终要记住“曙光在前头”和“千金难买回头看”,“千金难买回头看”是学习知识的...

1799
来自专栏mathor

matlab—回归与内插(完结)

1114
来自专栏吴伟祥

时间复杂度和空间复杂度详解 原

(1)时间频度 一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时...

632
来自专栏技术点滴

绝对均匀图生成算法一、何为绝对均匀图?二、简单讨论一下三、试一下递归?四、核心思想五、算法实现与测试

最近在研究图计算的性能,需要构造不同的测试数据对图算法进行压测,其中就涉及到均匀图的概念。

812
来自专栏小樱的经验随笔

2017 Multi-University Training Contest - Team 1 1003&&HDU 6035 Colorful Tree【树形dp】

Colorful Tree Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131...

2524
来自专栏数据小魔方

左手用R右手Python系列——因子变量与分类重编码

今天这篇介绍数据类型中因子变量的运用在R语言和Python中的实现。 因子变量是数据结构中用于描述分类事物的一类重要变量。其在现实生活中对应着大量具有实际意义的...

3085
来自专栏韦弦的微信小程序

Swift 实现strStr() - LeetCode

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

682
来自专栏窗户

有限域(3)——多项式环的商环构造有限域

  接着上两章内容,我们还是得继续寻找有限域的构造方法。上章证明矩阵环是个单环,自然是没戏了,但我们还可以考虑多项式环。

452

扫码关注云+社区