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().getSquare //> res1: Int = 9
Foo("word length").getSquare //cannot prove that String <:< Int
Foo().getLength //cannot prove that Int =:= String
class Foo[A]的类型参数可以是任何类型,意思是我们可以用任何类型来实例化Foo。然后我们在class内部用implicit ev更近一步的限定了A的类型。这样我们才能正确使用getLength和getSquare函数,否则发生编译错误。这个例子基本上能把=:=,<:<解释清楚了。
那么既然scalaz的<~<和===对应了<:<和=:=,那么先在上面的例子中用scalaz版本试试:
package Exercises
import 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 = 11
Foo().getSquare //> res1: Int = 9
Foo("word length").getSquare //could not find implicit value for parameter ev: scalaz.Liskov.<~<[String,Int]
Foo().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@9f70c54
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(),List(),List()).join //> res3: List[Int] = List(1, 2, 3)
List(.some,.some,.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的更多相关文章
- Python中为什么推荐使用isinstance来进行类型判断?而不是type
转自:http://www.xinxingzhao.com/blog/2016/05/23/python-type-vs-isinstance.html Python在定义变量的时候不用指明具体的的类 ...
- php上传常见文件类型对应的$_FILES["file"]["type"](转)
php上传常见文件类型对应的$_FILES["file"]["type"] from:http://hi.baidu.com/7book/item/374971 ...
- [.NET Core]ASP.NET Core中如何解决接收表单时的不支持的媒体类型(HTTP 415 Unsupported Media Type)错误呢?
[.NET Core]ASP.NET Core中如何解决接收表单时的不支持的媒体类型(HTTP 415 Unsupported Media Type)错误呢? 在ASP.NET Core应用程序中,接 ...
- C#中类型分析中的常见问题 Type - 转
http://www.cnblogs.com/yuanyuan/archive/2012/08/16/2642281.html 写代码的时候经常需要分析已有类型的信息例如:分析现有类型自动生成类, 或 ...
- 上传文件时文件类型限制 <input id="File1" type="file" accept=""/>
在做项目项目中经常需要上传文件,类型也就那几种.虽然在js中加了上传文件类型的限制,但是为了减少因为用户选择不当而造成的反复检验.可以在input标签上加上accept属性.这种限制只是优化了选择文件 ...
- Scalaz(27)- Inference & Unapply :类型的推导和匹配
经过一段时间的摸索,用scala进行函数式编程的过程对我来说就好像是想着法儿如何将函数的款式对齐以及如何正确地匹配类型,真正是一种全新的体验,但好像有点太偏重学术型了. 本来不想花什么功夫在scala ...
- 转载:oracle 自定义类型 type / create type
标签:type create oracle object record 一:Oracle中的类型有很多种,主要可以分为以下几类: 1.字符串类型.如:char.nchar.varchar2.nvarc ...
- oracle 自定义类型 type / create type
一:Oracle中的类型有很多种,主要可以分为以下几类: 1.字符串类型.如:char.nchar.varchar2.nvarchar2. 2.数值类型.如:int.number(p,s).integ ...
- 全面理解Python中的类型提示(Type Hints)
众所周知,Python 是动态类型语言,运行时不需要指定变量类型.这一点是不会改变的,但是2015年9月创始人 Guido van Rossum 在 Python 3.5 引入了一个类型系统,允许开发 ...
随机推荐
- PHP-CS-Fixer:格式化你的PHP代码
简介 良好的代码规范可以提高代码可读性,团队沟通维护成本.最推荐大家遵守的是 php-fig(PHP Framework Interop Group) 组织定义的 PSR-1 . PSR-2 两个.不 ...
- excel表格中如何将内容粘贴到筛选后的可见单元格[转]
默认情况下,筛选后excel表格进行复制粘贴,会贴到隐藏的表格. 可以添加两个辅助列来完成操作:1.在筛选前在表格右边添加"辅助1"列,在第二行输入1,按Ctrl+鼠标左键往下拉到 ...
- require.js 的使用
一.为什么要用require.js 在同一个页面要加载多个js文件时,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长: 其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比 ...
- ASP.NET 如何判断当前请求是否是Ajax请求
/// <summary> /// Description:验证用户是否登陆 /// Author:xucixiao /// Date:2013- ...
- Html与CSS快速入门04-进阶应用
这部分是html细节知识的学习. 快速入门系列--HTML-01简介 快速入门系列--HTML-02基础元素 快速入门系列--HTML-03高级元素和布局 快速入门系列--HTML-04进阶概念 之前 ...
- 大型.NET商业软件代码保护技术 技术与实践相结合保护辛苦创造的劳动成果
列举工作以来遇到的各种类型的软件所采用的代码保护技术,只讲原理不涉及技术细节实现,以避免产生法律问题.有些朋友说直接把代码放在Github开源下载,开源可以促进技术交流与进步,然而值钱的代码都积压在硬 ...
- javascript基础语法——表达式
× 目录 [1]原始表达式 [2]复杂表达式 前面的话 一般地,关于javascript基础语法,人们听得比较多的术语是操作符和语句.但是,其实还有一个术语经常使用,却很少被提到,这就是javascr ...
- 《BI那点儿事》Microsoft 时序算法——验证神奇的斐波那契数列
斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10 ...
- Linux - 终端语言设置
查看当前终端用户的语言设置 locale - get locale-specific information : locale |grep LANG 改变当前终端用户的语言设置(临时生效) 中文UTF ...
- 精品素材:15套免费的 Photoshop 自定义图形集
网上到处都是 Photoshop 笔刷,图案,纹理素材,最缺少的就是 Photoshop 形状.寻找定制的 Photoshop 形状是真的很难,因为很少有人提供这样的 Photoshop 形状的集合. ...