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)  }


在这两个函数的隐式参数分别使用了<~<和=== 。既然与标准scala库的<:<和=:=相对应,那么我们可以先了解一下<:<和=:=的用法:

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


class Foo[A]的类型参数可以是任何类型,意思是我们可以用任何类型来实例化Foo。然后我们在class内部用implicit ev更近一步的限定了A的类型。这样我们才能正确使用getLength和getSquare函数,否则发生编译错误。这个例子基本上能把=:=,<:<解释清楚了。

那么既然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类型系统的强大推断能力。通过提供一些类型的实例,它为我们产生了许多源代码。







0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 红米手机3x屏幕不灵怎么办? 乐视1s手机字库坏了怎么办 三星c7手机左右两按钮不亮怎么办 手机摔了一屏碎了下黑屏了怎么办 三星手机摔了一下黑屏了怎么办 行驶证一年扣分超过50分怎么办 朋友去广西传销现在骗我怎么办 行驶证忘带交警查住了怎么办 行驶证正本丢了副本在怎么办 在杭州驾照12分扣完了怎么办 驾照审验期过了40天了怎么办 自己的车借给别人撞死了人怎么办 无证驾驶报别人驾驶证被扣车怎么办 交了强制险但驾驶证过期了怎么办 考驾照科目一身份证掉了怎么办 驾照科目二考试身份证丢了怎么办 身份证遗失又要参加考试怎么办啊 驾驶本到期换本有扣分怎么办 b2驾驶证六年到期有扣分怎么办 驾考有效期是几年过期了怎么办? 驾考要过期了科四补考没过怎么办 驾驶证过期了可副业丢了怎么办 外地人北京驾驶本到期换本怎么办 报考驾照时电话号码填错了怎么办 报考驾照时电话填错了怎么办 邢台开三轮车驾证扣12分怎么办 新c1驾照扣满6分怎么办 b2驾照酒驾降级后再次酒驾怎么办 我b2驾照扣了9分怎么办 开别人的车出了事故怎么办 骑摩托车行驶证年检过期了怎么办 在两个城市车船税交重复了怎么办 车子被撞对方全责不赔钱怎么办 驾驶证暂扣期间该审证了怎么办 号码预约被不小心取消了怎么办 老婆出轨要跟我离婚我不想离怎么办 老婆要离婚我不想离电话拉黑怎么办 驾驶证到期换证体检有色弱怎么办 在长沙雨花区考的驾照丢了怎么办 两个户口注销了一个驾证怎么办 车管所发的初始密码弄丢了怎么办?