Scala的Higher-Kinded类型

Higher-Kinded从字面意思上看是更高级的分类,也就是更高一级的抽象。我们先看个例子。

如果我们要在scala中实现一个对Seq[Int]的sum方法,应该怎么做呢?

def sum(seq: Seq[Int]): Int = seq reduce (_ + _)sum(Vector(1,2,3,4,5)) // 结果值: 15

看起来很简单,刚刚我们实现了Seq[Int]的sum操作,那么如果我们想更进一步,我们想同时实现Seq[Int]和Seq[(Int,Int)]的操作该怎么处理呢?

不同的Seq需要不同的add实现,我们先抽象一个trait:

trait Add[T] {
def add(t1: T, T2: T): T
}

接下来我们在Add的伴生类中定义两个隐式实例,一个Add[Int], 一个Add[(Int,Int)]。

object Add {
implicit val addInt = new Add[Int] {
def add(i1: Int, i2: Int): Int = i1 + i2
}
implicit val addIntIntPair = new Add[(Int,Int)] {
def add(p1: (Int,Int), p2: (Int,Int)): (Int,Int) =
(p1._1 + p2._1, p1._2 + p2._2)
}
}

这两个隐式实例分别为Add[Int], 一个Add[(Int,Int)]实现了add方法。

最后我们可以定义sumseq方法了:

def sumSeq[T : Add](seq: Seq[T]): T =
seq reduce (implicitly[Add[T]].add(_,_))

T : Add 被称为 上下文定界( context bound), 它暗指隐式参数列表将接受Add[T] 实例。

我们看下怎么调用:

sumSeq(Vector(1 -> 10, 2 -> 20, 3 -> 30)) // 结果值: (6,60)
sumSeq(1 to 10) // 结果值: 55
sumSeq(Option(2)) // 出错!

sumSeq可以接受Seq[Int]和Seq[(Int,Int)]类型,但是无法接收Option。

对于任何一种序列,只要我们为它定义了隐式的Add 实例,那么sumSeq 方法便能计算出该序列的总和。

不过,sumSeq 仍然只支持Seq 子类型。假如容器类型并不是Seq 子类型,但是却实现了reduce 方法,我们该如何对该容器进行处理呢?我们会使用更加泛化的求和操作。这时候就需要使用到higher-kinded 类型了。

我们用M替代Seq,则可以得到M[T],M[T]就是本文介绍的Higher-Kinded类型。

trait Reduce[T, -M[T]] {
def reduce(m: M[T])(f: (T, T) => T): T
}
object Reduce {
implicit def seqReduce[T] = new Reduce[T, Seq] {
def reduce(seq: Seq[T])(f: (T, T) => T): T = seq reduce f
}
implicit def optionReduce[T] = new Reduce[T, Option] {
def reduce(opt: Option[T])(f: (T, T) => T): T = opt reduce f
}
}

为了能对Seq 和Option 值执行reduce操作,我们分别为这两类类型提供了隐式实例。为了简化起见,我们将直接使用类型中已经提供的reduce 方法执行reduce操作。

注意这里-M[T]是逆变类型,还记得我们之前的结论吗?函数的参数一定是逆变类型的。 因为M[T]是reduce(m: M[T])的参数,所以我们需要定义它为逆变类型-M[T]。

我们看一下sum方法该怎么定义:

def sum[T : Add, M[T]](container: M[T])(
implicit red: Reduce[T,M]): T =
red.reduce(container)(implicitly[Add[T]].add(_,_))

调用结果如下:

sum(Vector(1 -> 10, 2 -> 20, 3 -> 30)) // 结果值: (6,60)
sum(1 to 10) // 结果值: 55
sum(Option(2)) // 结果值: 2
sum[Int,Option](None) // 错误!

最后一个调用,我们为sum 方法添加的类型签名[Int, Opton] 会要求编译器将None 解释成Option[Int] 类型。假如不添加该类型签名,我们将得到编译错误:无法判断Option[T] 类型中的类型参数T 到底应该对应addInt 方法还是addIntIntPair 方法。

通过显式地指定类型,我们能够得到真正希望捕获的运行错误:我们不能对None 值调用reduce 方法。

在上面的sum方法中,sum[T : Add, M[T]], T: Add是上下文边界,我们也想定义M[T] 的上下文边界,比如M[T] : Reduce。
因为上下文边界只适用于包含单参数的场景,而Reduce 特征包
含两个类型参数,所以我们需要对Reduce进行改造:

trait Reduce1[-M[_]] {
def reduce[T](m: M[T])(f: (T, T) => T): T
}
object Reduce1 {
implicit val seqReduce = new Reduce1[Seq] {
def reduce[T](seq: Seq[T])(f: (T, T) => T): T = seq reduce f
}
implicit val optionReduce = new Reduce1[Option] {
def reduce[T](opt: Option[T])(f: (T, T) => T): T = opt reduce f
}
}

在新的reduce1中,只包含一个类型参数且属于higher-kinded 类型。

M[_]是上篇文章我们讲到的存在类型。T 参数被移至reduce 方法。

修改后的sum方法如下:

def sum[T : Add, M[_] : Reduce1](container: M[T]): T =
implicitly[Reduce1[M]].reduce(container)(implicitly[Add[T]].add(_,_))

我们定义了两个上下文边界,它们分别作用于Reduce1 和Add。而使用implicity 修饰的类型参数则能够区分出这两种不同的隐式值。

M[_]就是我们经常会看到的higher-kinded, higher-kinded虽然带给我们额外的抽象,但是使代码变得更加复杂。大家可以酌情考虑是否需要使用。

更多精彩内容且看:

  • 区块链从入门到放弃系列教程-涵盖密码学,超级账本,以太坊,Libra,比特币等持续更新
  • Spring Boot 2.X系列教程:七天从无到有掌握Spring Boot-持续更新
  • Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新
  • java程序员从小工到专家成神之路(2020版)-持续更新中,附详细文章教程

更多教程请参考 flydean的博客

Scala的Higher-Kinded类型相关推荐

  1. Scala类型系统——高级类类型(higher-kinded types)

    高级类类型就是使用其他类型构造成为一个新的类型,因此也称为 类型构造器(type constructors).它的语法和高阶函数(higher-order functions)相似,高阶函数就是将其它 ...

  2. scala 抽象方法_Scala抽象类型

    scala 抽象方法 The type whose identity is not known precisely can be termed as abstract type. 身份未知的类型可以称 ...

  3. Scala教程之:静态类型

    文章目录 泛类型 型变 协变 逆变 不变 类型上界 类型下界 内部类 抽象类型 复合类型 自类型 隐式参数 隐式转换 多态方法 类型推断 Scala是静态类型的,它拥有一个强大的类型系统,静态地强制以 ...

  4. Idea21.1.3版本中Scala默认设置带类型

    1.第一步打开idea[File]->[Setting] 2.搜索栏搜索scala 3.设置 4.(这一步最关键,必须设置)(如果写scala还是得选对钩才能显示类型,则再设置如下 5.完成,测 ...

  5. scala整型的类型及取值范围

  6. IDEA Scala自动生成变量类型设置教程

  7. 使用intellij查看scala变量的具体类型

    然后点击小灯泡

  8. Scala的自定义类型标记

    Scala的自定义类型标记 Scala中有很多千奇百怪的符号标记,看起来是那么的独特,就像是一杯dry martini-好像黑夜中的萤火虫,那么耀眼,那么出众. 好了言归正传,这一篇文章我们会讲一下S ...

  9. Scala入门到精通——第二十四节 高级类型 (三)

    本节主要内容 Type Specialization Manifest.TypeTag.ClassTag Scala类型系统总结 在Scala中,类(class)与类型(type)是两个不一样的概念. ...

  10. scala的两种变量类型 var 和 val

    scala的两种变量类型 var 和 valscala的两种变量类型 var 和 val 在Java中,您可以这样声明新变量: String s = "hello"; int i ...

最新文章

  1. rsyslog服务日志报错分析1
  2. BW report execution callstack
  3. 【计算机组成原理】数据表示和运算
  4. tablueau地图标记圆形_30天30张中国风地图,从青绿山水到秦砖汉瓦
  5. Android线程池详解
  6. C#下2\10\16进制互转代码总汇
  7. amcharts去除版权标志
  8. python进程池和线程池_python自带的进程池及线程池
  9. Codeforces Round #342 (Div. 2) D. Finals in arithmetic(想法题/构造题)
  10. Atiitt 图像处理的常见功能业务用途与类库与功能实现 目录 1. 常见业务场景 2 1.1. 缩略图 2 1.2. 判断图像大小分辨率要求 长度 宽度 2 1.3. 图像格式 转换,,黑白图像
  11. 千兆8光8电宽温环网工业以太网交换机8光8电全千兆二层管理型宽温工业级环网交换机 光纤自愈环网工业交换机
  12. 51单片机学习总结(七)红外通信和DS18B20
  13. linux设置合上电脑,CentOS7设置笔记本合盖不休眠
  14. ubuntu 20.04 修改文件夹默认图标
  15. antv g2字体阴影_antv-g2学习手册-中
  16. 五一入哪个牌子蓝牙耳机好?无线主动降噪蓝牙耳机排行榜
  17. 给出一个不多于五位的正整数,求出它是几位数
  18. nyoj 1273 宣传墙
  19. 云里黑白第八回——msconfig诊断启动,这辈子都不敢用了o(╥﹏╥)o出现问题,你的PIN不可用,禁用服务
  20. 一文搞懂linux时间片,硬件时钟,软件时钟,实时时钟,时间中断,墙上时间

热门文章

  1. 排序算法-06堆排序(python实现)
  2. 排序算法-05简单选择排序(python实现)
  3. EOJ_1039_最长连续公共子序列
  4. HDU4390(容斥原理)
  5. [luogu5142]区间方差
  6. delphi调用windows api
  7. BufferQueue 和 gralloc
  8. 贝壳app Authorization参数分析
  9. 谈谈前后端分离实践中如何提升RESTful API开发效率
  10. 柴树杉:面向数据科学领域的新语言,Go+蓄势待发