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]] {
  ////
  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 = 11
Foo(3).getSquare                                  //> res1: Int = 9
Foo("word length").getSquare   //cannot prove that String <:< Int
Foo(3).getLength               //cannot prove that Int =:= String

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

那么既然scalaz的<~<和===对应了<:<和=:=,那么先在上面的例子中用scalaz版本试试:

 1 package Exercises
 2 import scalaz._
 3 import Scalaz._
 4 import Liskov.<~<, Leibniz.===
 5 object evidence {
 6 case class Foo[A](a: A) {  //type A 可以是任何类型
 7   def getLength(implicit ev: A === String): Int = ev(a).length  //A必须是String
 8   def getSquare(implicit ev: A <~< Int): Int = ev(a) * ev(a) //A必须是Int或子类
 9 }
10 Foo("word length").getLength                      //> res0: Int = 11
11 Foo(3).getSquare                                  //> res1: Int = 9
12 Foo("word length").getSquare   //could not find implicit value for parameter ev: scalaz.Liskov.<~<[String,Int]
13 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] = [email protected]
implicitly[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类型系统的强大推断能力。通过提供一些类型的实例,它为我们产生了许多源代码。

时间: 2024-10-02 20:33:00

Scalaz(21)-类型例证:Liskov and Leibniz - type evidence的相关文章

Python中为什么推荐使用isinstance来进行类型判断?而不是type

转自:http://www.xinxingzhao.com/blog/2016/05/23/python-type-vs-isinstance.html Python在定义变量的时候不用指明具体的的类型,解释器会在运行的时候会自动检查 变量的类型,并根据需要进行隐式的类型转化.因为Python是动态语言,所以一般情 况下是不推荐进行类型转化的.比如"+"操作时,如果加号两边是数据就进行加法操 作,如果两边是字符串就进行字符串连接操作,如果两边是列表就进行合并操作,甚 至可以进行复数的运

golang类型断言的使用(Type Assertion)

第一部分 首先,转自https://studygolang.com/articles/3314对断言的基本介绍 golang的语言中提供了断言的功能.golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似.那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动

C#中类型分析中的常见问题 Type - 转

http://www.cnblogs.com/yuanyuan/archive/2012/08/16/2642281.html 写代码的时候经常需要分析已有类型的信息例如:分析现有类型自动生成类, 或者为现有的类自动增加一些功能总结了一点点经验以ClassA  a; 为例1. 通过typeof(ClassA) 或者 a.GetType() 获取类型信息, 推荐使用typef() 可以避免空引用,而且有的时候不需要构造一个ClassA的实例, typeof(ClassA)的性能一点都不差,不要把它

magento 修改产品类型的方法 change product type

很多时候,要把之前录入的单一产品转换成可配置的产品类型,在管理后台是不能直接修改的,只能修改数据库.其方法是: 进入数据库,找到catalog_product_entity这个表,在 type_id 字段,把 simple 改成 configurable. 改成其它类型也可以,MAGENTO总共有以下类型: simple grouped configurable virtual bundle downloadable 如果大批量的产品需要类型转换,可以在数据库里面使用命令操作.

Scalaz(4)- typeclass:标准类型-Equal,Order,Show,Enum

Scalaz是由一堆的typeclass组成.每一个typeclass具备自己特殊的功能.用户可以通过随意多态(ad-hoc polymorphism)把这些功能施用在自己定义的类型上.scala这个编程语言借鉴了纯函数编程语言Haskell的许多概念.typeclass这个名字就是从Haskell里引用过来的.只不过在Haskell里用的名称是type class两个分开的字.因为scala是个OOP和FP多范畴语言,为了避免与OOP里的type和class发生混扰,所以就用了typeclas

oracle 自定义类型 type / create type

一:Oracle中的类型有很多种,主要可以分为以下几类: 1.字符串类型.如:char.nchar.varchar2.nvarchar2. 2.数值类型.如:int.number(p,s).integer.smallint. 3.日期类型.如:date.interval.timestamp. 4.PL/SQL类型.如:pls_integer.binary_integer.binary_double(10g).binary_float(10g).boolean.plsql类型是不能在sql环境中使

命令类型查看type

type:类型 1.命令作用: type命令用来显示指定命令的类型,判断给出的命令是内部命令还是外部命令. 2.使用方式: type command 3.参数说明: -t:输出"file"."alias"或者"builtin",分别表示给定的指令为"外部指令"."命令别名"或者"内部指令": -p:如果给出的指令为外部指令,则显示其绝对路径: -a:在环境变量"PATH&quo

C#反射机制 Type类型

using System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Text;using System.Threading.Tasks; namespace 基于System.Type的反射{    class Program    {        static void Main(string[] args)        {            //1.使

读书笔记 C# Type类型与泛型有关的某些属性浅析

IsGenericType 如果类型为泛型,则返回 true. GetGenericArguments 返回 Type 对象数组,这些对象表示为构造类型提供的类型变量,或泛型类型定义的类型参数.如果是MyList<int,Person> ,则返回int和Person类型的数组,如同Type[] tpyes={typeof(int),typeof(Person)},Type数组中任一参数的IsGenericParameter为false;如果是MyList<,>或,则返回T和U类型的