Scala之“逆变”合理性的思考_scala substitute-程序员宅基地

技术标签: 合理性  逆变  scala  Scala语言  Contravari  示例  

Scala之“逆变”合理性的思考

对于逆变的概念可以参考本系列的前一篇文章: Scala之类型参数化:Type Parameterization 本文的重点是要解释“逆变”的合理性。本文原文出处: http://blog.csdn.net/bluishglc/article/details/52585991 严禁任何形式的转载,否则将委托CSDN官方维护权益!

在思考“逆变”的合理性这个问题上,我们需要清晰地认识到一个前提,即父类与子类之间的关系实质,我们说:如果类A是类B的父类,那么所有出现类A声明的地方,我们都可以使用类B的实例进行替换,或者说所有适用于类A的操作同样适用于类B,简言之就是子类型可以透明无害地替换父类型(也就是里氏替换原则),因为子类型一定也是父类型,但父类型未必一定是子类型(有其他子类型),上述原则就是大家所熟知的里氏替换原则。

Liskov Substitution Principle (里氏替换原则)

It is safe to assume that a type T is a subtype of a type U if you can substitute a value of type T wherever a value of type U is required. 

The principle holds if T supports the same operations as U and all of T’s operations require less and provide more than the corresponding operations in U.

在回顾完上述表述之后,我们来重新审视一下“逆变”存在的合理性。首先定义如下一个Animal类族:

scala> class Animal
defined class Animal

scala> class Bird extends Animal 
defined class Bird

scala> class Dog extends Animal
defined class Dog

现在有f1,f2两个函数:

def f1(x: Bird): Unit // instance of Function1[Bird, Unit]
def f2(x: Animal): Unit // instance of Function1[Animal, Unit]

在这里,f1是f2的父类。为什么?我们知道,Function1的类型声明是Function1[-T1,+R],即函数是虽参数类型逆变,返回值类型协变的。其中随返回值类型协变是很容易理解的,随参数类型逆变往往让人费解,对此,我们同样使用前面提到的原则进行判定:父类可以被子类替换,反之则不可以,但是这里的情况会稍微有些复杂,因为我们要判断的是函数类型之间的可替换关系(即父子关系),我们可以认为函数是一种“复合”类型,它们的类型是由它们的参数和返回值的类型决定的,因此我们可以很自然的延展出这样一个规则:对于具有相同参数列表类型和返回值类型的函数,如果传给函数1的参数类型同样可以传给函数2,而传给函数2的参数未必都能传给函数1,也就是说,只从参数部分考量,函数1可以被函数2替换,即函数1是父类,函数2 是子类。

对于f2,我们说传给它一个Animal实例它可以工作,传给它一个Bird实例它仍然可以工作,在传给它一个Bird实例时,我们就要注意到,这时的f2(仅看参数部分)实例的类型实际上就已经变成f1了,这时所有声明使用f1类型的地方都可以用f2的实例去替换,但是反过来,所有声明了使用f2类型的地方我们是不能用f1的实例去替换的,因为对于f2来说,它可以接受Animal类型的任何其他子类型,比如Dog,但是Dog类型显然不适用于f1的。所以总结起来,f1可以被f2替换,但是f2不能被f1替换,所以f1是f2的父类型!

让我们再延伸地思考一下,我们可以说:因为f2是“消费”(consume)一个较为“通用”的父类型,这使得函数f2本身自然地能接纳和处理给定参数类型的所有子类型,也就意味着f2可以去替换或赋值给那些所有声明使用“具体”子类型为参数的函数,比如f1, 所以f1是父类,f2是子类!这种“消费”关系决定了逆变存在的理由,可以表述为PECS原理:

PECS stands for producer-extends, consumer-super.
In other words, if a parameterized type represents a T producer, use <? extends T>;
if it represents a T consumer, use <? super T>.

上述PECS原则换一种方法表述为:

G[+A]类似一个生产者,提供数据。(大部分情况下称G为容器类型)
G[-A] 是一个消费者,主要用来消费数据。(参考垃圾桶和垃圾的例子)

尽管我们依然在使用里氏替换原则来分析和识别“逆变”的场景,但是我们不能不承认这种解释依然只是一种逻辑上的逆推,它的解释总是让人觉得不是那么“解痒”,在本文的最后,我试图从正面给出一种“逆变”合理性的解释:

**我们说在现实世界里,如果有一类物品专门针对另一类物品而存在,除了人们一般认为的伴随着被处理物品的细化,处理品本身需要不断地跟进细化,这是“协变”的场景,也确实有可能会存在另外一种完全相反的情形:即伴随着被处理物品的细化,在掌握了越来越多处被理物品的信息和特征的趋势下,处理物品本身却可以变的愈发的简单(处理面变窄),反倒是那些处理更通用物品的处理类复杂的多,因为它们要考虑的可能的情况更多更复杂,那么这种情形就是典型的“逆变”!

一个典型是例子是空调和遥控器,如果说遥控器是基于空调类型的范型类,那么它天然应该是逆变的,即:RemoteController[-T], 空调品牌和型号越细化,遥控器实际上越单一,实现起来也越简单,反倒是随着空调类型不断地向上抽象,遥控器会变得越加复杂,直到面向所有空调通用的遥控器RemoteController[AirConditioner]诞生,这也就是我们见到过的那种万能遥控器。万能遥控器可以替换任何品牌和型号的遥控器,因此它是它们的子类!

我们可以看到大多数的逆变类有如下一些特点:

  • 如果逆变类有一个类族,那么这个类族不会是自上而下的树状结构,而是多条单线继承的路径组合,比如:RemoteController[AirConditioner] <: RemoteController[Haier]; RemoteController[AirConditioner] <: RemoteController[Gree]等等
  • 逆变类的父类和子类虽然作为父类和子类有替换关系,但是却没有任何继承关系,逆变类的子类之所以能够替换父类往往是它涵盖了父类的功能(针对某个更具体形变类型实现功能),而不存在继承父类实现的动作,因此逆变类的子类实现起来反而更加复杂。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/bluishglc/article/details/52585991

智能推荐

Sklearn中predict_proba函数用法及原理详解-程序员宅基地

文章浏览阅读4k次,点赞3次,收藏11次。Sklearn中predict_proba函数用法及数学原理详解(以logistic回归为例)_predict_prob

MVCC解决什么问题?原理是什么?_mvcc解决了什么问题-程序员宅基地

文章浏览阅读1.4k次。MVCC解决什么问题?原理是什么?_mvcc解决了什么问题

(邱维声)高等代数课程笔记:解线性方程组的矩阵消元法_比较消元法和初等变换求线性方程组的异同,并阐述自己的收获-程序员宅基地

文章浏览阅读383次。根据邱维声老师的高等代数课程,整理的笔记。_比较消元法和初等变换求线性方程组的异同,并阐述自己的收获

android商城首页demo,FanZhengxi-程序员宅基地

文章浏览阅读281次。Android HyBridge 开发一、三种App开发方式对比1. Native App特点:UI元素、数据内容、逻辑架构都安装在手机终端,导致不可跨平台,每次版本升级都要重新打包。缺点:无法跨平台、升级麻烦、开发成本高(指跨平台开发成本高)优点:速度快,用户体验好。2. Web App定义:可理解为移动端的网站,将网页部署在服务器上,用户通过各大浏览器来访问。缺点:页面访问速度慢、用户体验差。..._安卓应用商店app demo

好烦!快让ChatGPT停止道歉!SD创作宣传图的超细教程;教你在PH冷启动薅流量;CSDN举办AI应用创新大赛 | ShowMeAI日报_inscode deecamp x csdn ai应用创新大赛-程序员宅基地

文章浏览阅读318次。Stable Diffusion 图生图知识思维导图;使用 5W1H 框架启动一个可控的AI项目;培生集团将生成式AI学习工具引入在线高等教育平台;前 Meta AI 高管离职创业,做教育类 ChatGPT 应用……点击阅读全文_inscode deecamp x csdn ai应用创新大赛

【21天算法挑战赛】查找算法——顺序查找_普通查找是顺序查找么-程序员宅基地

文章浏览阅读814次,点赞9次,收藏7次。作者简介:我目前是一个在校学生,现在不敢说自己擅长什么,但是我想通过自己的学习努力让自己的技术、知识都慢慢提升,希望我们一起学习呀~。有话想说:写博客、记笔记并不是一种自我感动,把学到的东西记在脑子里才是最重要的,在这个过程中,不要浮躁,希望我们都可以越来越优秀!由于算法不会改变原有的元素集合,只需要一个额外的变量控制索引变化,所以空间复杂度为常数级:O(1)️兴趣领域:目前偏向于前端学习 算法学习。语言说明:代码实现我会用Python/C++~空间复杂度:O(1)

随便推点

JAVA常用实用类-程序员宅基地

文章浏览阅读1.1k次,点赞33次,收藏21次。javaAPI(java Application Programming Interface)即java应用程序编程接口,它是运行库的集合,预先定义了一些接口和类,程序员可以直接使用这些已经被打包的接口和类开发具体的应用来节约大量的时间和精力。API除了有"应用程序编程接口"的意思,还特指API的说明文档,也被称为API帮助文档。java语言的强大之处在于它提供了多种多样的类库,从而大大提高了程序的编程效率和质量,javaAPI提供常用的包如下。

常见python库学习(一)Tkinter库_GUI界面设计_python窗体类库-程序员宅基地

文章浏览阅读541次。提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、tkinter窗口1.简介2.Tkinter 控件二、1.引入库2.读入数据总结前言提示:这里可以添加本文要记录的大概内容:例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。提示:以下是本篇文章正文内容,下面案例可供参考一、tkinter窗口1.简介Tkinter 是使用 python 进行窗口视窗设计的模块,是 python 自带的、可_python窗体类库

通过mtd读写flash_基于STM32F7通过cube软件配置:读写QSPI接口64M flash和64M PSRAM参考教程...-程序员宅基地

文章浏览阅读825次。基于STM32F7通过cube软件配置:读写QSPI接口64M flash和64M PSRAM参考教程核心板:NUCLEO-F767ZIFlash:NM25Q64EB(64M)PSRAM:IPS6404L(64M)配置文件请使用stm32cubemx打开程序请使用keil5MDK打开NUCLEO-F767ZI上引出的QSPI引脚如下,NM25Q64EB和IPS6404L都是分别接到同样..._stm32f7 单片机 64引脚

获取栅格图层(Raster)的属性表_r raster getvalue-程序员宅基地

文章浏览阅读388次。pNewRaster是你的Raster图层IRasterBandCollection pRasterBC =(IRasterBandCollection ) pNewRaster;IRasterBand pRasterBand = pRasterBC.Item(0);ITable pTable = pRasterBand.AttributeTable;IQueryFilter pQueryFilter=new QueryFilterClass ();pQueryFilter .WhereClau._r raster getvalue

python反编译class文件_python反编译之字节码-程序员宅基地

文章浏览阅读226次。如果你曾经写过或者用过 Python,你可能已经习惯了看到 Python 源代码文件;它们的名称以.Py 结尾。你可能还见过另一种类型的文件是 .pyc 结尾的,它们就是 Python “字节码”文件。(在 Python3 的时候这个 .pyc 后缀的文件不太好找了,它在一个名为__pycache__的子目录下面。).pyc文件可以防止Python每次运行时都重新解析源代码,该文件大大节省了时间。..._python反编译class文件

hdu 2084 数塔(动态规划)-程序员宅基地

文章浏览阅读38次。本题是一个经典的动态规划题。直接利用记忆化搜索:见图解Ac code :#include<stdio.h>#include<string.h>#define max(i,j) (i>j?i:j)#define maxn 105int a[maxn][maxn];int d[maxn][maxn];int s(int i,...

推荐文章

热门文章

相关标签