经过一段时间的摸索,用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 :类型的推导和匹配相关推荐

  1. 解决:org.xml.sax.SAXParseException: 元素类型 “head“ 必须由匹配的结束标记 “</head>问题

    解决:org.xml.sax.SAXParseException: 元素类型 "head" 必须由匹配的结束标记 "问题 参考文章: (1)解决:org.xml.sax. ...

  2. SpringBoot中访问Thymeleaf提示:元素类型 meta 必须由匹配的结束标记终止。

    场景 新建SpringBoot项目后整合Thymelaf后访问页面提示: 元素类型 "meta" 必须由匹配的结束标记 "</meta>" 终止. ...

  3. 使用thymeleaf的时候报元素类型“meta“必须由匹配的结束标记

    使用thymeleaf的时候报元素类型"meta"必须由匹配的结束标记""终止> 解决方案有二种: 第一种: 让html的标记严格严谨的语法: 加上结束标 ...

  4. C++ 异常类型以及多级catch匹配

    exceptionType是异常类型,它指明了当前的 catch 可以处理什么类型的异常:variable是一个变量,用来接收异常信息.当程序抛出异常时,会创建一份数据,这份数据包含了错误信息,程序员 ...

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

    Android 中文件类型与MIME的匹配表 转载于:https://www.cnblogs.com/zhujiabin/p/5669008.html

  6. MySQL8.0.27 修改编码类型(utf8mb3)

    文章目录 前言 一.运行环境 二.更换步骤 1.查看编码类型 2.更改配置文件 前言 笔者最近在完成课程实验作业使用MySQL时需要修改编码类型为utf8,但是由于版本为8.0.27,搜寻到的操作建议 ...

  7. 【2019.11.27】EM算法详细推导

    EM算法 无隐变量下,极大似然函数为: L(θ)=∏iP(xi;θ)L(\theta) = \prod_iP\left(x^{i};\theta\right) L(θ)=i∏​P(xi;θ) 含隐变量 ...

  8. Android 中文件类型与MIME的匹配表(转)

    背景介绍: MIME:全称Multipurpose Internet Mail Extensions,多功能Internet 邮件扩充服务.它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮 ...

  9. 谷歌空间从服务器检索信息时出错,谷歌,火狐提示来自http://xxx.com/file的资源已被阻止,因为 MIME 类型(text/plain)不匹配(X-Content-Type-Opt...

    在使用ueditor编辑的过程中无法上传图片,谷歌火狐浏览器提示Cross-Origin Read Blocking (CORB) blocked cross-origin response http ...

最新文章

  1. awk4.0 — awk格式化
  2. 原创 | 变分自动编码器(VAE)
  3. 光流 | 基于LK(Lucas-Kanade)光流算法的运动目标检测
  4. 图解Java常用数据结构
  5. 定义一个结构体指针需要分配存储空间?
  6. Win11任务栏空白怎么办 Win11任务栏空白解决办法
  7. 计算机密码忘了 开不了机怎么办,电脑设了开机密码现在忘了开不了机怎么处理?...
  8. 通俗易懂的堆排序C++实现
  9. 大数据之-Hadoop环境搭建_hadoop目录结构---大数据之hadoop工作笔记0019
  10. 2018年1月3日-江苏地税系统无法正常登陆的解决方案
  11. SQL 2012 镜像 图解(解决1418)
  12. ApiPost自动化测试基础之:接口参数依赖的情景处理...
  13. 如何监测系统用户是否登录_网站建设教程:PageAdmin Cms如何获取用户登录状态...
  14. 数据库 软件实施 工程师
  15. TiDB DevCon2018.tick(1.20)
  16. 知识图谱-生物信息学-医学顶刊论文(Briefings in Bioinformatics-2021):生物信息学中的图表示学习:趋势、方法和应用
  17. Android so文件浅析
  18. uboot命令之bootm详解
  19. 大数据———Flume与Kafka整合
  20. 智能运维之告警聚合技术介绍

热门文章

  1. VTK:相互作用之Game
  2. VTK:Filtering之WarpTo
  3. C语言以递归实现插入排序Insertion Sort算法(附完整源码)
  4. STL里resize和reserve的区别?
  5. Linux执行yum不显示时间图形,Linux停的yum命令详解(朝花夕拾)
  6. python网络编程教学_python网络编程学习初步
  7. Dubbo之——将Dubbo服务打包成Jar包
  8. 本地编译Hadoop2.8.0源码总结和问题解决(转自:http://blog.csdn.net/young_kim1/article/details/50324345)
  9. 4.事务提交过程,事务基本概念,Oracle中的事务生命周期,保存点savepoint,数据库的隔离级别
  10. struts2之单个文件上传(特别推荐)