Scala的Higher-Kinded类型
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类型相关推荐
- Scala类型系统——高级类类型(higher-kinded types)
高级类类型就是使用其他类型构造成为一个新的类型,因此也称为 类型构造器(type constructors).它的语法和高阶函数(higher-order functions)相似,高阶函数就是将其它 ...
- scala 抽象方法_Scala抽象类型
scala 抽象方法 The type whose identity is not known precisely can be termed as abstract type. 身份未知的类型可以称 ...
- Scala教程之:静态类型
文章目录 泛类型 型变 协变 逆变 不变 类型上界 类型下界 内部类 抽象类型 复合类型 自类型 隐式参数 隐式转换 多态方法 类型推断 Scala是静态类型的,它拥有一个强大的类型系统,静态地强制以 ...
- Idea21.1.3版本中Scala默认设置带类型
1.第一步打开idea[File]->[Setting] 2.搜索栏搜索scala 3.设置 4.(这一步最关键,必须设置)(如果写scala还是得选对钩才能显示类型,则再设置如下 5.完成,测 ...
- scala整型的类型及取值范围
- IDEA Scala自动生成变量类型设置教程
- 使用intellij查看scala变量的具体类型
然后点击小灯泡
- Scala的自定义类型标记
Scala的自定义类型标记 Scala中有很多千奇百怪的符号标记,看起来是那么的独特,就像是一杯dry martini-好像黑夜中的萤火虫,那么耀眼,那么出众. 好了言归正传,这一篇文章我们会讲一下S ...
- Scala入门到精通——第二十四节 高级类型 (三)
本节主要内容 Type Specialization Manifest.TypeTag.ClassTag Scala类型系统总结 在Scala中,类(class)与类型(type)是两个不一样的概念. ...
- scala的两种变量类型 var 和 val
scala的两种变量类型 var 和 valscala的两种变量类型 var 和 val 在Java中,您可以这样声明新变量: String s = "hello"; int i ...
最新文章
- rsyslog服务日志报错分析1
- BW report execution callstack
- 【计算机组成原理】数据表示和运算
- tablueau地图标记圆形_30天30张中国风地图,从青绿山水到秦砖汉瓦
- Android线程池详解
- C#下2\10\16进制互转代码总汇
- amcharts去除版权标志
- python进程池和线程池_python自带的进程池及线程池
- Codeforces Round #342 (Div. 2) D. Finals in arithmetic(想法题/构造题)
- Atiitt 图像处理的常见功能业务用途与类库与功能实现 目录 1. 常见业务场景	2 1.1. 缩略图	2 1.2. 判断图像大小分辨率要求 长度 宽度	2 1.3. 图像格式 转换,,黑白图像
- 千兆8光8电宽温环网工业以太网交换机8光8电全千兆二层管理型宽温工业级环网交换机 光纤自愈环网工业交换机
- 51单片机学习总结(七)红外通信和DS18B20
- linux设置合上电脑,CentOS7设置笔记本合盖不休眠
- ubuntu 20.04 修改文件夹默认图标
- antv g2字体阴影_antv-g2学习手册-中
- 五一入哪个牌子蓝牙耳机好?无线主动降噪蓝牙耳机排行榜
- 给出一个不多于五位的正整数,求出它是几位数
- nyoj 1273 宣传墙
- 云里黑白第八回——msconfig诊断启动,这辈子都不敢用了o(╥﹏╥)o出现问题,你的PIN不可用,禁用服务
- 一文搞懂linux时间片,硬件时钟,软件时钟,实时时钟,时间中断,墙上时间