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虽然带给我们额外的抽象,但是使代码变得更加复杂。大家可以酌情考虑是否需要使用。
更多教程请参考 flydean的博客
Scala的Higher-Kinded类型的更多相关文章
- Scala函数---既存类型
语法: Type ::= InfixType ExistentialClauses ExistentialClauses ::= „forSome‟ „{‟ ExistentialDcl {semi ...
- Programming In Scala笔记-第十七章、Scala中的集合类型
本章主要介绍Scala中的集合类型,主要包括:Array, ListBuffer, Arraybuffer, Set, Map和Tuple. 一.序列 序列类型的对象中包含多个按顺序排列好的元素,可以 ...
- Programming In Scala笔记-第五章、Scala中的变量类型和操作
这一章的一些基础性的东西,主要包括Scala中的基本变量类型,以及相关的一些操作符. 一.简单类型 下表中列出Scala语言中的基本类型,以及其字节长度,其中Byte, Short, Int, Lon ...
- Scala进阶之路-Scala中的高级类型
Scala进阶之路-Scala中的高级类型 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.类型(Type)与类(Class)的区别 在Java里,一直到jdk1.5之前,我们说 ...
- Scala类型系统——高级类类型(higher-kinded types)
高级类类型就是使用其他类型构造成为一个新的类型,因此也称为 类型构造器(type constructors).它的语法和高阶函数(higher-order functions)相似,高阶函数就是将其它 ...
- Scalaz(30)- Free :Natural Tranformation ~> - map higher kinded types for free
当我们需要定义一些对应高阶类型进行相互类型转换的操作函数时,我们发现scala语言并不提供能定义这种函数的支持.举例来说:如果我们希望定义一个函数把对于任何T值的Option[T]转换成List[T] ...
- Scala学习笔记--集合类型Queue,Set
补充知识:http://www.importnew.com/4543.html 正文开始 scala.collection.immutable scala.collection.mutable 队列Q ...
- java.lang.String cannot be cast to scala.runtime.Nothing Scala中的Nothing类型
经常在写Rdd的时候, 如: val OWNER_ID=row.getAs("OWNER_ID") 等, 运行是可能会报异常 : java.lang.String cannot ...
- Scala学习笔记(2)-类型注意
Scala类型注意事项: 1.Any是绝对的根,所有的其他可实例化类型均有AnyVal和AnyRef派生. 2.所有AnyVal的类型成为值类型(所有数值类型.char.Booble和Unit) 3. ...
- Scala的类与类型
类和类型 List<String>和List<Int>类型是不一样的,但是jvm运行时会采用泛型擦除.导致List<String>和List<Int>都 ...
随机推荐
- Vertica的这些事(三)——Vertica中实现Oracle中的ws_concat功能
vertica中没有类似Oracle中的ws_concat函数功能,需要开发UDF,自己对C++不熟悉,所有只有想其他方法解决了. 上代码: SELECT node_state, MAX(DECODE ...
- es6的箭头函数和es5的function函数区别
一.es6的箭头函数es6箭头函数内部没有this,使用时会上朔寻找最近的this不可以做构造函数,不能使用new命令,因为没有this函数体内没有arguments,可以使用rest参数代替不能用y ...
- VUE一款适用于pc平台的简单toast
新项目要求用typescript+vue+elementui的模式来搭建pc项目,最初踩了好多坑.产品说提示不想用element-ui的提示. 打算用toast的形式.所以就自己写了一个pc的toas ...
- 前端面试题解密:经典算法之冒泡算法(ES6版)及优化
前言 随着前端的飞速发展,前端业务开发给前端工程师提出了更高的要求,因而算法题也越来越高频次的出现在前端面试中.有很多的小伙伴找胡哥苦诉,在前端实际开发中(除了涉及游戏开发方面),算法使用有很多吗?大 ...
- 【PHP】流程控制
一. 顺序结构 a) PHP的脚本从上到下,从左往右的执行顺序就是顺序结构! 二. 分支结构 a) 单一的分支结构 If ( 条件表达式 ) { 程序体: } 执行过程 ...
- 选择排序(C++,Java,Python实现)
排序算法之选择排序,选择排序,选择排序的基本思想描述为:每一趟在n-i+1(i=1,2,-,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录.具体来说,假设长度为n的数组arr,要按照从小 ...
- "Flex弹性布局"组件:<flex-row><flex-col> —— 快应用组件库H-UI
 <import name="flex-row" src="../Common/ui/h-ui/basic/c_flex_row"></im ...
- CH5701 开车旅行(倍增dp+set)
传送门 解题思路: 一道比较有趣的题,解题工作主要分为两块: ①找出k(k=0表示小A先走,k=1表示小B先走,下面同理)从点i出发下一个到达的点to[k][i]; 一开始偷懒用了vector(偷懒一 ...
- Markdown 语法自用
Markdown 语法自用 1. 标题 用#来标记 hi hihi hihihi hihihiih hihihihihi hihihihihihi 2. 段落格式 2.1 字体 斜体:文字两端加上 ...
- 原生js实现扇形导航以及动画的坑
第一次发博客,有点紧张.首先来一张效果图. 主要是实现了点击右下角的风扇按钮实现了: 导航栏的开启与关闭,中间伴随着 transition过渡以及transform的2D动画. 上源码: <!D ...