scala 协变和逆变 在函数上的应用

本文探讨Scala中的协变和逆变概念,通过具体示例解释它们在函数和容器类中的应用。协变使得子类型的对象可以赋值给父类型的变量,而逆变则影响函数参数类型。文章通过List和Function1特性的分析,展示了如何利用协变和逆变增强类型系统的灵活性。
摘要由CSDN通过智能技术生成


假设A <: B, 可以简单认为A是B的子类(A <: B的具体定义是:任何B满足的性质,A都满足):

A<:B的前提下,有如下对协变逆变的定义:

C[A] <: C[B]                                     C 是 协变的 covariant

C[A] >: C[B]                                    C 是 逆变的 contravariant

C[A]和C[B]都不是彼此的子类    C是 非变的 nonvariant


C是AnyRef类,即引用类,例如Int是值类,List则是引用类,这是因为List需要定义其元素的类型,比如List[Int]

C[A]代表C的元素是A类对象,C[A]<:C[B]代表C[B]是C[A]基类,因此C[B]可以赋值给C[A]


Scala可以用下面的方式更简单声明容器类的性质

Class C[+A] {..}                                           C 是 协变的 covariant

Class C[-A] {…}                                           C 是 逆变的 contravariant

Class C[A]{…}                                     C是 非变的 nonvariant



例子

 

Trait List[+T]{

 Def perpend(elem:T):List[T] = new Cons(elem, this)

}

假设基类Inset有两个子类nonEmpty和empty

这个定义是错误的,当objectA 是List[nonempty], 语句:objectA.prepend(empty) 会导致类型匹配错误。

 


1.List[+T]的用处:当有对象A为List[NonEmpty], 则可以把对象A赋值给一个需要List[Inset]的参数,因为List类是协变的------nonEmpty是Inset子类,List[NonEmpty]便是List[Inset]的子类)

2. prepend是属于trait List[+T]的方法

出现错误的情形:

假设此时this是一个List[nonempty]的对象,

则List[T](List[+T]赋予List的性质在这里不用考虑了),编译器会识别T是nonEmpty类,所以如果this.prepend(empty)会导致类型错误,因为empty和nonEmpty都是InSet的子类,但是empty不是nonEmpty的子类,不能赋值给需要nonEmpty的参数位置(elem:T)。


正确的定义方式:

Def perpend[U>:T](elem: U) :List[U] =new Cons(elem, this)

U>:T 表示:U是T的基类,T是U的子类。因此原先List[nonEmpty]的对象,T是nonEmpty,但是U是T的基类,所以U是Inset。传入prepend方法的元素要求是Inset类型,empty是Inset子类当然可以传入,得到的new Cons(empty, this)是List[Inset],毫无问题

 

应用

 

 

1 ).选择支持协变的容器类:

1. Inset class 中有NoEmpty 和 empty两个子类。

2. Array不是协变,即定义abstractclass Array[T] extends Seq[T]。

3. 如果Array换成List, 因为定义abstractclass List[+T] extends Seq[T],List是协变,则NonEmpty是Inset的子类,List[NonEmpty]是List[Inset]的子类,第二行的a可以赋值给b。类似于c++的多态,子类指针和子类引用是可以分别赋值给基类指针和基类引用

 

2). 定义新的函数对象,例如

object addOne extends Function1[Int,Int]{
  def apply(m:Int):Int= m + 1
}

利用函数的协变:

If A2 <: A1 并且 B1<:B2 则有:

 A1=> B1 <: A2 => B2

因此scala中的Function1特性的定义如下:

Package scala

Trait Function1[-T, +U] {
  def apply(x:T) : U

}

假设有两个Function1:

 

scala> val f1: Int => String = x=> s"Int($x)"

f1: Int => String = <function1>

 

scala> val f2: Any => String = x=> s"Any($x)"

f2: Any => String = <function1>

 

凡是f1可以使用的地方,f2都是可以用的(考虑基类子类的关系:基类可以使用的场景,子类都可以使用);但反过来不行。

所以类型Function1[Any, String]应该是Function1[Int, String]的子类, 是Trait Function1[-T, +U]

 的定义赋予了Function1类更丰富的继承关系。



逆变的传入类型,允许子类在重写基类的函数时,传入参数的类型比基类原函数定义的传入参数类型更广泛。


而协变的返回类型,允许了子类在重写基类的函数时,可以返回比基类原函数定义的返回类型更加具体的类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值