Scala之类型参数化:Type Parameterization

Scala之类型参数化:Type Parameterization

本文原文出处: http://blog.csdn.net/bluishglc/article/details/52584401 严禁任何形式的转载,否则将委托CSDN官方维护权益!

型变:Variance

型变试图在规范和回答这样一个问题:如果T’是T的一个子类,那么Container[T’]应该被看做是Container[T]的子类吗?对于这个问题有三种可能的结果,它们分别如下:

种类含义Scala标记
协变 CovariantC[T’] 是 C[T] 的子类[+T]
逆变 ContravariantC[T] 是 C[T’]的子类[-T]
不变 InvariantC[T] 和 C[T’]无关[T]

不变: Invariant

“不变”性表述为:虽然T’是T的一个子类,但是Container[T’]和Container[T]之间无任何关联,不存在任何父子关系。

协变:Covariant

协变是比较符合人们的正常思维的,因此理解起来并不困难,简单解释就是:如果T’是T的一个子类,那么Container[T’]应该被看做是Container[T]的子类。下面的例子展示了协变[+T]的特性。

scala> class Covariant[+T]
defined class Covariant

scala> val cv: Covariant[AnyRef] = new Covariant[String]
cv: Covariant[AnyRef] = Covariant@4035acf6

scala> val cv: Covariant[String] = new Covariant[AnyRef]
<console>:6: error: type mismatch;
 found   : Covariant[AnyRef]
 required: Covariant[String]
       val cv: Covariant[String] = new Covariant[AnyRef]
                                   ^

逆变:Contravariant

逆变正好是协变的”反”方向:如果T’是T的一个子类,那么Container[T’]应该被看做是Container[T]的父类。这初看起来是非怪异并不符合情理的。在列举它的适用场景之前,我们同样来先看一下解释逆变的例子:

scala> class Contravariant[-A]
defined class Contravariant

scala> val cv: Contravariant[String] = new Contravariant[AnyRef]
cv: Contravariant[AnyRef] = Contravariant@49fa7ba

scala> val fail: Contravariant[AnyRef] = new Contravariant[String]
<console>:6: error: type mismatch;
 found   : Contravariant[String]
 required: Contravariant[AnyRef]
       val fail: Contravariant[AnyRef] = new Contravariant[String]
                                     ^

很显然,上述例子中AnyRef是String的父类,但是Contravariant[AnyRef]则变成了Contravariant[String]的子类,从而可以进行赋值操作(变量声明为父类型,引用的是子类型实例)

逆变初看上去有些离经叛道,对于逆变的合理性,本系列有一篇专门的文章: Scala之“逆变”合理性的思考 ,对逆变进行了深入的思考。

类型参数的边界控制

在引入了范型之后,有时候我们需要对接受类型的范围进行一些限制,特别在引入了协变和逆变之后,更需要对于协变的下界和逆变的上界进行界定。

下界:Lower Bound

表述形式: U >: T

这种表述的含义是:U必须是类型T或T的父类。>:有点类似>=的意思。

如前所属,协变往往需要用到下界,例如:

scala> class Consumer[+T](t: T) {
     |  def f1(t: T) = {}
     | }
<console>:12: error: covariant type T occurs in contravariant position in type T of value t
        def f1(t: T) = {}
                ^

scala> class Consumer[+T](t: T) {
     | def f1[U >: T](u : U) = {println(u)}
     | }
defined class Consumer

上述示例中Consumer是伴随类型T协变的,而f1的类型是Function1[-T1,+R],即要求类型T是逆变的,这样函数f1和类Consumer对类型T的要求就产生了冲突,比如Consumer可以声明使用T的一个子类型,而这个子类型是不能适用到f1上的。为了解决这个问题,我们可以针对f1的类型参数进行下界控制。

Scala官方文档中也有一个下界的使用示例:http://docs.scala-lang.org/tutorials/tour/lower-type-bounds

上界:Upper Bound

表述形式: S <: T

这种表述的含义是:S必须是类型T或T的子类。<:有点类似<=的意思。

Scala官方文档中有一个上界的使用示例:

    trait Similar {
      def isSimilar(x: Any): Boolean
    }
    case class MyInt(x: Int) extends Similar {
      def isSimilar(m: Any): Boolean =
        m.isInstanceOf[MyInt] &&
        m.asInstanceOf[MyInt].x == x
    }
    object UpperBoundTest extends App {
      def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean =
        if (xs.isEmpty) false
        else if (e.isSimilar(xs.head)) true
        else findSimilar[T](e, xs.tail)
      val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
      println(findSimilar[MyInt](MyInt(4), list))
      println(findSimilar[MyInt](MyInt(2), list))
    }

在这个例子中我们注意第12行,如果没有上界声明:T <: Similar,被声明为T的实例e是不能调用isSimilar方法的。

视界:View Bound

表述形式: A <% B

这种表述的含义是:类型A必须“可被视为”类型B,“可被视为”的意思就是存在一个将类A转换为类型B的隐式转换!下面这段示意代码演示的就是把类型A的实例当作了类型B去使用,因为存在一个从A到B的隐式转换。

def f[A <% B](a: A) = a.bMethod

而实际上,上述代码会被编译为:

def f[A](a: A)(implicit ev: A => B) = a.bMethod

在scala的类库中有这样典型的应用:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

对于任何可以隐式转换为Ordered[A]类型的类型A,都可以直接使用比较大小的方法<。

注:Scala社区已不再推荐使用视界: https://github.com/scala/scala/pull/2909

上下文边界:Context Bound

让我们通过如下一段示例代码来了解一下Context Bound:

import math.Ordering
case class MyList[A](list: List[A]) {
    def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): List[A] = list.sortBy(f)(ord)
    def sortBy2[B : Ordering](f: A => B): List[A] = list.sortBy(f)(implicitly[Ordering[B]])
}
val list = MyList(List(1,3,5,2,4))
list sortBy1 (i => -i)
list sortBy2 (i => -i)

sortBy1和sortBy2的效果是一样的,即针对一个集合,依据一个函数,把集合中的每个元素转换成另外一种类型的值,然后根据转化后的类型的排序规则( Ordering[B]的一个实例)进行排序。sortBy1是一种直白写法,而sortBy2是一种从语法上更为简洁的实现,或者说sortBy2实际上会被编译成sortBy1的形式。

简单地说,当我们看到一个Context Bound时,有两点我们应该立刻想到,以这里的[B : Ordering]为例:

  1. 方法已经自动引入了一个隐式参数implicit ord: Ordering[B]
  2. 如果在方法中需要使用这个隐式参数,可以通过Predef定义的方法implicitly来获取,如本例中做的那样:implicitly[Ordering[B]]

每当我们使用到Context Bound时,我们都可以通过上述的转换来理解Context Bound。当然,当你已经非常了解这中间发生的故事之后,你会就会适应这种简洁的语法而不再需要在脑袋里进行这种转换转换。

关于View Bound和Context Bound,可以参考Scala官方文档的一篇文章: http://docs.scala-lang.org/tutorials/FAQ/context-and-view-bounds.html

参考:

https://twitter.github.io/scala_school/zh_cn/type-basics.html

http://www.spoofer.top/2016/03/12/scala-%E5%8F%98%E4%B8%8E%E7%95%8C

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laurence 

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值