Scalaz(21)-类型例证:Liskov and Leibniz - type evidence
来源:互联网 发布:时代互联域名 编辑:程序博客网 时间:2024/05/17 03:37
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]] { //// import Liskov.<~<, Leibniz.=== def flatMap[B](f: A => F[B]) = F.bind(self)(f) def >>=[B](f: A => F[B]) = F.bind(self)(f) def ∗[B](f: A => F[B]) = F.bind(self)(f) def join[B](implicit ev: A <~< F[B]): F[B] = F.bind(self)(ev(_)) def μ[B](implicit ev: A <~< F[B]): F[B] = F.bind(self)(ev(_)) def >>[B](b: => F[B]): F[B] = F.bind(self)(_ => b) def ifM[B](ifTrue: => F[B], ifFalse: => F[B])(implicit ev: A === Boolean): F[B] = { val value: F[Boolean] = ev.subst(self) F.ifM(value, ifTrue, ifFalse) } ////}
原来Liskov和Leibniz都是scalaz库的type class。Leskov <~< 和 Leibniz === 都是类型操作符号,实际上是scalaz自己版本的类型限制操作符 <:< 和 =:= 。发现这两个函数看起来特别奇怪才打起了彻底了解一下Leskov和Leibeniz的主意:
def join[B](implicit ev: A <~< F[B]): F[B] = F.bind(self)(ev(_)) def ifM[B](ifTrue: => F[B], ifFalse: => F[B])(implicit ev: A === Boolean): F[B] = { val value: F[Boolean] = ev.subst(self) F.ifM(value, ifTrue, ifFalse) }
A =:= B 意思是A必须是B类型的,如:A =:= Int 意思是A必须是Int类型的。而A <:< B 意思是A必须是B的子类,或者说是我们可以在任何时候用A来替代B。那么既然已经知道了A的类型为什么还需要再框定它呢?实际上的确在某些场合需要对A的类型进行进一步框定,看看下面的例子:
case class Foo[A](a: A) { //type A 可以是任何类型 def getLength(implicit ev: A =:= String): Int = a.length //A必须是String def getSquare(implicit ev: A <:< Int): Int = a * a //A必须是Int或子类}Foo("word length").getLength //> res0: Int = 11Foo(3).getSquare //> res1: Int = 9Foo("word length").getSquare //cannot prove that String <:< IntFoo(3).getLength //cannot prove that Int =:= String
那么既然scalaz的<~<和===对应了<:<和=:=,那么先在上面的例子中用scalaz版本试试:
package Exercisesimport scalaz._import Scalaz._import Liskov.<~<, Leibniz.===object evidence {case class Foo[A](a: A) { //type A 可以是任何类型 def getLength(implicit ev: A === String): Int = ev(a).length //A必须是String def getSquare(implicit ev: A <~< Int): Int = ev(a) * ev(a) //A必须是Int或子类}Foo("word length").getLength //> res0: Int = 11Foo(3).getSquare //> res1: Int = 9Foo("word length").getSquare //could not find implicit value for parameter ev: scalaz.Liskov.<~<[String,Int]Foo(3).getLength //could not find implicit value for parameter ev: scalaz.Leibniz.===[Int,String]
再看看原理,就用scalaz版的作为研究对象吧。因为Liskov和Leibniz都是scalaz的type class,对于隐性参数我们要进行隐式转换解析。先看看Leibniz的一些定义:scalaz/Leibniz.scala
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]...
先不用理会这些类型参数限定,很乱,总之绕来绕去就是A和B在一个类型区域内。值得注意的是apply,和subst这个抽象函数:输入参数F[A]返回结果F[B]。因为A === String其实就是Leibniz[A,String]的一种表达方式,我们需要解析Leibniz实例。在Leibniz.scala内发现了这个:
object Leibniz extends LeibnizInstances with LeibnizFunctions{ /** `(A === B)` is a supertype of `Leibniz[L,H,A,B]` */ type ===[A,B] = Leibniz[⊥, ⊤, A, B]}和trait LeibnizFunctions { import Leibniz._ /** Equality is reflexive -- we rely on subtyping to expand this type */ implicit def refl[A]: Leibniz[A, A, A, A] = new Leibniz[A, A, A, A] { def subst[F[_ >: A <: A]](p: F[A]): F[A] = p } /** We can witness equality by using it to convert between types * We rely on subtyping to enable this to work for any Leibniz arrow */ implicit def witness[A, B](f: A === B): A => B = f.subst[({type λ[X] = A => X})#λ](identity) implicit def subst[A, B](a: A)(implicit f: A === B): B = f.subst[Id](a)...
当我们尝试找寻Leibniz[A,String]实例时唯一可能就只有Leibniz[A,A,A,A],类型转换其实就是通过把subst的传入参数转变成返回结果。我们可以用下面的方法证明:
implicitly[Int === Int] //> res2: scalaz.Leibniz.===[Int,Int] = scalaz.LeibnizFunctions$$anon$2@9f70c54implicitly[String === Int] //could not find implicit value for parameter e: scalaz.Leibniz.===[String,Int]
ev(a)就是apply(a)=subst[Id](a)=a, 暗地里subst帮助了类型转换A=>String,这点我们可以通过调换A和String的位置来再次证明:
def getLength(implicit ev: String === A): Int = ev(a).length //type mismatch; found : A required: String
同样的我们可以看看Liskov定义:scalaz/Liskov.scala
sealed abstract class Liskov[-A, +B] { def apply(a: A): B = Liskov.witness(this)(a) def subst[F[-_]](p: F[B]): F[A]
同样是这个subst函数:首先F[-_]是逆变,F[B]=>F[A]需要A是B的子类。隐式转换解析:
object Liskov extends LiskovInstances with LiskovFunctions { /**A convenient type alias for Liskov */ type <~<[-A, +B] = Liskov[A, B] /**A flipped alias, for those used to their arrows running left to right */ type >~>[+B, -A] = Liskov[A, B]}和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 } /**We can witness equality by using it to convert between types */ implicit def witness[A, B](lt: A <~< B): A => B = { type f[-X] = X => B lt.subst[f](identity) } /**Subtyping is reflexive */ implicit def refl[A]: (A <~< A) = new (A <~< A) { def subst[F[-_]](p: F[A]): F[A] = p }...
我们可以看到在 A <~< B 实例的类型转换函数subst中输入参数F[B]直接替代返回结果F[A],因为F[]是逆变(contravariant)而A是B的子类。也就是我们可以用A替代B。
好,我们试试分析上面提到的join函数。众所周知,join函数是Monad的打平函数(flaten function)。这个版本可以在这里找到:scalaz/Bind.scala
/** Sequence the inner `F` of `FFA` after the outer `F`, forming a * single `F[A]`. */ def join[A](ffa: F[F[A]]) = bind(ffa)(a => a)
这个容易理解。但我们现在面对的是这个版本:scalaz/BindSyntax.scala
def join[B](implicit ev: A <~< F[B]): F[B] = F.bind(self)(ev(_))
这里使用了Leskov,我们看看到底发生了什么:
List(List(1),List(2),List(3)).join //> res3: List[Int] = List(1, 2, 3)List(1.some,2.some,3.some).join //could not find implicit value for parameter ev: scalaz.Liskov.<~<[Option[Int],List[B]]
正确的ev实例需要Liskov[List[List[Int]],List[Int]],List[List[Int]]是List[Int]的子类。在subst函数里输入参数F[B]直接替代了返回结果F[A]。那么:
F.bind(List[List[A]])(ev(List[A]))
=F.bind(List[List[A]])(witness(Leskov[List[List[Int]],List[Int]])(List[List[Int]])
=F.bind(List[List[A]])(List[Int])
=List[Int]
我们看到List[List[Int]]被witness转换成List[Int]。
上面的分析好像很神奇,但我们隐约可以感受到scala类型系统的强大推断能力。通过提供一些类型的实例,它为我们产生了许多源代码。
- Scalaz(21)-类型例证:Liskov and Leibniz - type evidence
- Scalaz(44)- concurrency :scalaz Future,尚不完整的多线程类型
- Scalaz(8)- typeclass:Monoid and Foldable
- Scalaz(46)- scalaz-stream 基础介绍
- Scalaz(47)- scalaz-stream: 深入了解-Source
- Scalaz(49)- scalaz-stream: 深入了解-Sink/Channel
- Scalaz(4)- typeclass:标准类型-Equal,Order,Show,Enum
- Scalaz(17)- Monad:泛函状态类型-State Monad
- Scalaz(27)- Inference & Unapply :类型的推导和匹配
- Scalaz(5)- typeclass:my typeclass scalaz style-demo
- Scalaz(50)- scalaz-stream: 安全的无穷运算-running infinite stream freely
- Scalaz(51)- scalaz-stream: 资源使用安全-Resource Safety
- Scalaz(52)- scalaz-stream: 并行运算-parallel processing concurrently by merging
- Scalaz(53)- scalaz-stream: 程序运算器-application scenario
- Scalaz(54)- scalaz-stream: 函数式多线程编程模式-Free Streaming Programming Model
- Scalaz(48)- scalaz-stream: 深入了解-Transducer: Process1-tee-wye
- Scalaz(55)- scalaz-stream: fs2-基础介绍,fs2 stream transformation
- Scalaz(56)- scalaz-stream: fs2-安全运算,fs2 resource safety
- 银行卡号与开户行对照文档
- android录像及播放
- debug current instruction pointer
- 时差,不同国家之间的大概时差
- 学习过程中的快思考与慢思考
- Scalaz(21)-类型例证:Liskov and Leibniz - type evidence
- 一些移动开发的前端框架分享
- HDOJ 1302-The Snail
- Fld selectn for mvmt type 551 / acct 400001 differs for Customer goods movement (015) Message no. M7
- java类加载的先后顺序
- SQL Server审计功能入门:SQL Server审核 (SQL Server Audit)
- 【Hibernate3】(3)数据库生成策略
- 文档的编码格式问题
- 你真的理解大数据吗?