数学基础
- 线性代数
- 概率论与随机过程
- 数值计算
- 蒙特卡洛方法与 MCMC 采样
- 机器学习方法概论
统计学习
深度学习
- 深度学习简介
- 深度前馈网络
- 反向传播算法
- 正则化
- 深度学习中的最优化问题
- 卷积神经网络
- CNN:图像分类
- 循环神经网络 RNN
- Transformer
- 一、Transformer [2017]
- 二、Universal Transformer [2018]
- 三、Transformer-XL [2019]
- 四、GPT1 [2018]
- 五、GPT2 [2019]
- 六、GPT3 [2020]
- 七、OPT [2022]
- 八、BERT [2018]
- 九、XLNet [2019]
- 十、RoBERTa [2019]
- 十一、ERNIE 1.0 [2019]
- 十二、ERNIE 2.0 [2019]
- 十三、ERNIE 3.0 [2021]
- 十四、ERNIE-Huawei [2019]
- 十五、MT-DNN [2019]
- 十六、BART [2019]
- 十七、mBART [2020]
- 十八、SpanBERT [2019]
- 十九、ALBERT [2019]
- 二十、UniLM [2019]
- 二十一、MASS [2019]
- 二十二、MacBERT [2019]
- 二十三、Fine-Tuning Language Models from Human Preferences [2019]
- 二十四 Learning to summarize from human feedback [2020]
- 二十五、InstructGPT [2022]
- 二十六、T5 [2020]
- 二十七、mT5 [2020]
- 二十八、ExT5 [2021]
- 二十九、Muppet [2021]
- 三十、Self-Attention with Relative Position Representations [2018]
- 三十一、USE [2018]
- 三十二、Sentence-BERT [2019]
- 三十三、SimCSE [2021]
- 三十四、BERT-Flow [2020]
- 三十五、BERT-Whitening [2021]
- 三十六、Comparing the Geometry of BERT, ELMo, and GPT-2 Embeddings [2019]
- 三十七、CERT [2020]
- 三十八、DeCLUTR [2020]
- 三十九、CLEAR [2020]
- 四十、ConSERT [2021]
- 四十一、Sentence-T5 [2021]
- 四十二、ULMFiT [2018]
- 四十三、Scaling Laws for Neural Language Models [2020]
- 四十四、Chinchilla [2022]
- 四十七、GLM-130B [2022]
- 四十八、GPT-NeoX-20B [2022]
- 四十九、Bloom [2022]
- 五十、PaLM [2022] (粗读)
- 五十一、PaLM2 [2023](粗读)
- 五十二、Self-Instruct [2022]
- 句子向量
- 词向量
- 传统CTR 预估模型
- CTR 预估模型
- 一、DSSM [2013]
- 二、FNN [2016]
- 三、PNN [2016]
- 四、DeepCrossing [2016]
- 五、Wide 和 Deep [2016]
- 六、DCN [2017]
- 七、DeepFM [2017]
- 八、NFM [2017]
- 九、AFM [2017]
- 十、xDeepFM [2018]
- 十一、ESMM [2018]
- 十二、DIN [2017]
- 十三、DIEN [2019]
- 十四、DSIN [2019]
- 十五、DICM [2017]
- 十六、DeepMCP [2019]
- 十七、MIMN [2019]
- 十八、DMR [2020]
- 十九、MiNet [2020]
- 二十、DSTN [2019]
- 二十一、BST [2019]
- 二十二、SIM [2020]
- 二十三、ESM2 [2019]
- 二十四、MV-DNN [2015]
- 二十五、CAN [2020]
- 二十六、AutoInt [2018]
- 二十七、Fi-GNN [2019]
- 二十八、FwFM [2018]
- 二十九、FM2 [2021]
- 三十、FiBiNET [2019]
- 三十一、AutoFIS [2020]
- 三十三、AFN [2020]
- 三十四、FGCNN [2019]
- 三十五、AutoCross [2019]
- 三十六、InterHAt [2020]
- 三十七、xDeepInt [2023]
- 三十九、AutoDis [2021]
- 四十、MDE [2020]
- 四十一、NIS [2020]
- 四十二、AutoEmb [2020]
- 四十三、AutoDim [2021]
- 四十四、PEP [2021]
- 四十五、DeepLight [2021]
- 图的表达
- 一、DeepWalk [2014]
- 二、LINE [2015]
- 三、GraRep [2015]
- 四、TADW [2015]
- 五、DNGR [2016]
- 六、Node2Vec [2016]
- 七、WALKLETS [2016]
- 八、SDNE [2016]
- 九、CANE [2017]
- 十、EOE [2017]
- 十一、metapath2vec [2017]
- 十二、GraphGAN [2018]
- 十三、struc2vec [2017]
- 十四、GraphWave [2018]
- 十五、NetMF [2017]
- 十六、NetSMF [2019]
- 十七、PTE [2015]
- 十八、HNE [2015]
- 十九、AANE [2017]
- 二十、LANE [2017]
- 二十一、MVE [2017]
- 二十二、PMNE [2017]
- 二十三、ANRL [2018]
- 二十四、DANE [2018]
- 二十五、HERec [2018]
- 二十六、GATNE [2019]
- 二十七、MNE [2018]
- 二十八、MVN2VEC [2018]
- 二十九、SNE [2018]
- 三十、ProNE [2019]
- Graph Embedding 综述
- 图神经网络
- 一、GNN [2009]
- 二、Spectral Networks 和 Deep Locally Connected Networks [2013]
- 三、Fast Localized Spectral Filtering On Graph [2016]
- 四、GCN [2016]
- 五、神经图指纹 [2015]
- 六、GGS-NN [2016]
- 七、PATCHY-SAN [2016]
- 八、GraphSAGE [2017]
- 九、GAT [2017]
- 十、R-GCN [2017]
- 十一、 AGCN [2018]
- 十二、FastGCN [2018]
- 十三、PinSage [2018]
- 十四、GCMC [2017]
- 十五、JK-Net [2018]
- 十六、PPNP [2018]
- 十七、VRGCN [2017]
- 十八、ClusterGCN [2019]
- 十九、LDS-GNN [2019]
- 二十、DIAL-GNN [2019]
- 二十一、HAN [2019]
- 二十二、HetGNN [2019]
- 二十三、HGT [2020]
- 二十四、GPT-GNN [2020]
- 二十五、Geom-GCN [2020]
- 二十六、Graph Network [2018]
- 二十七、GIN [2019]
- 二十八、MPNN [2017]
- 二十九、UniMP [2020]
- 三十、Correct and Smooth [2020]
- 三十一、LGCN [2018]
- 三十二、DGCNN [2018]
- 三十三、AS-GCN
- 三十四、DGI [2018]
- 三十五、DIFFPOLL [2018]
- 三十六、DCNN [2016]
- 三十七、IN [2016]
- 图神经网络 2
- 图神经网络 3
- 推荐算法(传统方法)
- 一、Tapestry [1992]
- 二、GroupLens [1994]
- 三、ItemBased CF [2001]
- 四、Amazon I-2-I CF [2003]
- 五、Slope One Rating-Based CF [2005]
- 六、Bipartite Network Projection [2007]
- 七、Implicit Feedback CF [2008]
- 八、PMF [2008]
- 九、SVD++ [2008]
- 十、MMMF 扩展 [2008]
- 十一、OCCF [2008]
- 十二、BPR [2009]
- 十三、MF for RS [2009]
- 十四、 Netflix BellKor Solution [2009]
- 推荐算法(神经网络方法 1)
- 一、MIND [2019](用于召回)
- 二、DNN For YouTube [2016]
- 三、Recommending What Video to Watch Next [2019]
- 四、ESAM [2020]
- 五、Facebook Embedding Based Retrieval [2020](用于检索)
- 六、Airbnb Search Ranking [2018]
- 七、MOBIUS [2019](用于召回)
- 八、TDM [2018](用于检索)
- 九、DR [2020](用于检索)
- 十、JTM [2019](用于检索)
- 十一、Pinterest Recommender System [2017]
- 十二、DLRM [2019]
- 十三、Applying Deep Learning To Airbnb Search [2018]
- 十四、Improving Deep Learning For Airbnb Search [2020]
- 十五、HOP-Rec [2018]
- 十六、NCF [2017]
- 十七、NGCF [2019]
- 十八、LightGCN [2020]
- 十九、Sampling-Bias-Corrected Neural Modeling [2019](检索)
- 二十、EGES [2018](Matching 阶段)
- 二十一、SDM [2019](Matching 阶段)
- 二十二、COLD [2020 ] (Pre-Ranking 模型)
- 二十三、ComiRec [2020](https://www.wenjiangs.com/doc/0b4e1736-ac78)
- 二十四、EdgeRec [2020]
- 二十五、DPSR [2020](检索)
- 二十六、PDN [2021](mathcing)
- 二十七、时空周期兴趣学习网络ST-PIL [2021]
- 推荐算法之序列推荐
- 一、FPMC [2010]
- 二、GRU4Rec [2015]
- 三、HRM [2015]
- 四、DREAM [2016]
- 五、Improved GRU4Rec [2016]
- 六、NARM [2017]
- 七、HRNN [2017]
- 八、RRN [2017]
- 九、Caser [2018]
- 十、p-RNN [2016]
- 十一、GRU4Rec Top-k Gains [2018]
- 十二、SASRec [2018]
- 十三、RUM [2018]
- 十四、SHAN [2018]
- 十五、Phased LSTM [2016]
- 十六、Time-LSTM [2017]
- 十七、STAMP [2018]
- 十八、Latent Cross [2018]
- 十九、CSRM [2019]
- 二十、SR-GNN [2019]
- 二十一、GC-SAN [2019]
- 二十二、BERT4Rec [2019]
- 二十三、MCPRN [2019]
- 二十四、RepeatNet [2019]
- 二十五、LINet(2019)
- 二十六、NextItNet [2019]
- 二十七、GCE-GNN [2020]
- 二十八、LESSR [2020]
- 二十九、HyperRec [2020]
- 三十、DHCN [2021]
- 三十一、TiSASRec [2020]
- 推荐算法(综述)
- 多任务学习
- 系统架构
- 实践方法论
- 深度强化学习 1
- 自动代码生成
工具
- CRF
- lightgbm
- xgboost
- scikit-learn
- spark
- numpy
- matplotlib
- pandas
- huggingface_transformer
- 一、Tokenizer
- 二、Datasets
- 三、Model
- 四、Trainer
- 五、Evaluator
- 六、Pipeline
- 七、Accelerate
- 八、Autoclass
- 九、应用
- 十、Gradio
Scala
- 环境搭建
- 基础知识
- 函数
- 类
- 样例类和模式匹配
- 测试和注解
- 集合 collection(一)
- 集合collection(二)
- 集成 Java
- 并发
五、隐式类型转换
Scala
支持隐式类型转换:这是一个定义为implicit
的方法。xxxxxxxxxx
class C(n:Int,s:String) { val num:Int = n val name:String = s } implicit def CtoInt(c:C) = c.num // 隐式类型转换 val c = new C(123,"Hello") println(1+c) // 输出:124为了让隐式类型转换能够工作,它需要定义在作用域内。
由于隐式类型转换是由编译器隐式的作用在代码上,而不是在代码中显式的给出。因此对于使用方程序员,究竟哪些地方隐式转换起了作用并不是那么直观。因此这会阻碍代码的可读性。
隐式定义指的是我们允许编译器插入程序以解决类型错误的定义。例如:如果
x + y
无法通过编译,则编译器可能将它修改为convert(x) + y
,其中convert
是某种可用的隐式转换。如果
convert
是一种简单的转换函数,则不应该在代码里显式写出从而有助于澄清程序的主要逻辑。隐式转换规则:
标记规则:只有标记为
implicit
的定义才可用。关键字
implicit
用于标记哪些声明可以被编译器用作隐式定义。可以用implicit
来标记任何变量、函数或者对象的定义。编译器只会从那些显式标记为implicit
的定义中选择。作用域规则:被插入的隐式转换必须是当前作用域的单个标识符,或者跟隐式转换的源类型或者目标类型有关联。
Scala
编译器只会考虑那些在当前作用域内的隐式转换,因此必须以某种方式将隐式转换定义引入到当前作用域。除了一个例外,隐式转换在当前作用域中必须是单个标识符。如对于
x + y
,编译器不会插入someVar.convert(x)
这种形式的转换,你必须显式导入import someVar.convert
来使用单个标识符convert
。事实上对于类库而言,通常提供一个包含了一些有用的隐式转换的Preamble
对象,这样使用这个类库的代码就可以通过import Preamble._
来访问该类库的隐式类型转换。但是这个规则有一个例外:编译器还会在隐式转换的源类型或者目标类型的伴生对象中查找隐式定义。我们不需要在程序中
import
伴生对象。每次一个规则:每次只能有一个隐式定义被插入。
如:编译器绝对不会将
x + y
重写为convert1(convert2(x)) + y
。可以通过让隐式定义包含隐式参数的方式绕过这个限制。
显式优先原则:只要代码按照编写的样子能够通过类型检查,就不要尝试隐式定义。
编译器不会对已经可以工作的代码做修改。
这个规则必然可以得出结论:我们总是可以将隐式标识符替代成显式的,代码会更长但是歧义更少。这是一种折衷:
- 如果代码看上去重复啰嗦,则隐式转换可以减少这种繁琐的代码
- 如果代码看上去生硬晦涩,则可以显式的插入转换
究竟是否采用隐式转换,这是代码风格问题。
隐式转换可以使用任何名称。隐式转换的名称只有两种情况下重要:
- 当你希望显式给出转换时
- 当决定程序的哪些位置都有哪些隐式转换可用时。
考虑一个带有两个隐式转换的对象:
xxxxxxxxxx
object MyConversions { implicit def stringWrapper(s :String) : IndexedSeq[Char] = ... implicit def intToString(x :Int): String = ... }如果你只是希望使用
stringWrapper
转换,并不希望使用intToString
转换,则可以通过仅仅引入其中一个来实现:xxxxxxxxxx
import MyConversions.stringWrapper在这里,隐式转换的名字很重要,因为只有这样才可以有选择的引入一个而不引入另外一个。
Scala
会在三个地方使用隐式定义:转换到一个预期的类型:在预期不同类型的上下文中使用某个类型。如你有一个
String
,但是你要将它传递给一个要求IndexedSeq[Char]
的方法。对某个(成员)选择接收端(即字段、方法调用)的转换:适配接收端的类型。
如
"abc".exists
将转换为stringWrapper("abc").exists
,因为exists
方法在String
上不可用,但是在IndexedSeq
上是可用的。隐式参数:用于给被调用函数提供更多关于调用者诉求的信息。
隐式参数对于泛型函数尤其有用,被调用的函数可能完全不知道某个或某些入参的类型。
5.1 转换到一个预期的类型
每当编译器看到一个
X
而它需要一个Y
的时候,他就会查找一个能将X
转换为Y
的隐式转换。将
Double
隐式转换成Int
可能并不是一个好主意,因为这会悄悄的丢掉精度。我们更推荐从一个受限的类型转换成更通用的类型。如:
scala.Predef
,它是每个Scala
程序都会隐式导入的对象,它定义了一些从 “更小” 的数值类型到 “更大” 的数值类型的隐式转换。如scala.Predef
定义了:xxxxxxxxxx
implicit def int2double(x: Int): Double = x.toDoubleScala
中并没有什么强制类型转换,所有的类型转换都是通过这种隐式转换或者显式转换来实现的。
5.2 转换接收端
隐式转换还能应用于方法调用的接收端,也就是调用方法的那个对象。这种隐式转换有两个用途:
- 允许我们更平滑的将新类集成到已有的类继承关系图谱当中。
- 支持在语言中编写(原生的)领域特定语言
DSL
。
即如你写了
obj.doIt
,而obj
并没有一个doIt
的成员,编译器会在放弃之前尝试插入转换。这里转换需要应用于接收端,也就是
obj
。编译器会装作obj
的预期“类型”为“拥有“名字为doIt
的成员。这个”类型“ 并不是一个普通的scala
类型,不过从概念上讲它是存在的。接收端转换的一个主要用途是:让新类型和已有类型的集成更为平滑。
如定义了新类型:
xxxxxxxxxx
class MyCls(n: Int, d: Int){ ... def + (that: MyCls): MyCls = ... def + (that: Int): Mycls = ... }则我们可以通过以下调用:
xxxxxxxxxx
val obj = new MyCls(1,2) obj + 1但是无法通过以下调用:
xxxxxxxxxx
1 + obj因为作为接收端的
1
并没有一个合适的方法+ (MyCls)
。为了允许这样的操作,我们可以定义一个Int
到MyCls
的隐式转换:xxxxxxxxxx
implicit def intToMyCls(x: Int) = new MyCls(x, 1) 1 + obj背后的原理:
Scala
编译器首先尝试对表达式1 + obj
进行类型检查。虽然Int
有多个+
方法,但是没有一个是接收参数为MyCls
类型的,因此类型检查失败。接着编译器检查一个从Int
到另一个拥有可以应用MyCls
参数的+
方法的类型的隐式转换,编译器将会找到intToMyCls
隐式转换并执行:xxxxxxxxxx
intToMyCls(1) + obj隐式转换的另一个主要用途是模拟添加新的语法。
考虑创建一个
Map
:xxxxxxxxxx
Map("a" -> 1, "b" -> 2)这里的
->
并不是scala
的语法特性,而是ArrowAssoc
类的方法。ArrowAssoc
是一个定义在scala.Predef
对象这个scala
标准前导preamble
代码里的类。当写下"a" -> 1
时,编译器将插入一个从"a"
到ArrowAssoc
的转换。下面是相关定义:
xxxxxxxxxx
package scala object Predef{ class ArrowAssoc[A](x: A){ def -> [B](y: B): Tuple2[A,B] = Tuple2(x,y) } implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x) ... }这种“富包装类”模式在给编程语言提供
syntax-like
的扩展的类库中非常常见。- 只要你看到有人调用了接受类中不存在的方法,那么很可能使用了隐式转换。
- 如果你看到名为
RichSomething
的类(如:RichInt,RichBoolean
),那么这个类很可能对Something
类型增加了syntax-like
的方法。
富包装类的应用场景广泛,它可以让你做出以类库形式定义的内部
DSL
。Scala 2.10
引入隐式类来简化富包装类的编写。隐式类是一个以implicit
关键字开始的类。对这样的类,编译器会生成一个从类的构造方法参数到类本身的隐式转换。如:
xxxxxxxxxx
case class Rect(width: Int, height: Int) implicit class RectMaker(width: Int){ def x(height: Int) = Rect(width, height) } val myRect = 3 x 4其调用过程为:
- 由于
Int
类型没有一个名为x
的方法,因此编译器会查找一个从Int
到某个有该方法的类型的隐式转换。 - 编译器将找到类
RectMaker
并执行转换,然后调用RectMaker
的x
方法。
并不是所有的类都可以作为隐式类。
隐式类不能是样例类。
隐式类的构造方法必须有且仅有一个参数。
隐式类必须存在于另一个对象、类或者特质里。
在实际应用中,只要是用隐式类作为富包装类来给某个已有的类添加方法,该限制不是问题。
- 由于
5.3 隐式参数
编译器有时候将
f(a)
替换为f(a)(b)
,或者将new C(a)
替换成new C(a)(b)
,通过追加一个参数列表的方式来完成某个函数调用。隐式参数调用提供的是整个最后一组柯里化的参数列表,而不仅仅是最后一个参数。如果
f
缺失的最后一个参数列表有三个参数,那么编译器将f(a)
替换成f(a)(b,c,d)
。此时,不仅被插入的标识符,如b,c,d
需要在定义时标记为implicit
,f
的最后一个参数列表在定义时也需要标记为implicit
。示例:
xxxxxxxxxx
class A(val name: String) { ... } object O{ def f(s: String)(implicit a: A)={ println(s + " " + a.name) } }你可以显式调用:
xxxxxxxxxx
val a = new A("a1") O.f("hello")(a)也可以隐式调用,因为
f
的最后一个参数列表标记为implicit
:xxxxxxxxxx
implicit val imp_a = new A("implicit a") O.f("hello")当调用
O.f("hello")
时,编译器自动填充最后一个参数列表。但是这里必须首先在作用域内找到一个符合要求的类型的implicit
变量,这里为imp_a
。注意
implict
变量必须为当前作用域内的单个标识符,如:xxxxxxxxxx
object impObj{ implicit val imp_a = new A("implict a") } O.f("hello")这种调用在编译期间报错,必须将
imp_a
引入到O.f("hello")
的作用域。示例:
xxxxxxxxxx
class A(val name: String) { ... } class B(val name: String) { ... } object O{ def f(s: String)(implicit a: A, b: B)={ println(s + " " + a.name + " " + b.name) } } object impObj{ implicit val imp_a = new A("implict a") implicit val imp_b = new B("implict b") } import impObj._ O.f("hello")这里
implicit
关键字是应用到整个参数列表而不是单个参数。隐式参数通常都是采用那些非常“稀有”或者“特别”的类型,防止意外匹配。
隐式参数最常用的场景是:提供关于更靠前的那个参数列表中已经”显式“(与”隐式“相对应)提到的类型的信息。
如:下面的函数给取出列表中的最大元素:
xxxxxxxxxx
def maxElement[T](elements: List[T])(implicit ordering: Ordering[T]): T = { elements match { case List() => throw new IllegalArgumentException("empty list!") case List(x) => x case x :: rest => val maxRest = maxElement(rest)(ordering) if(ordering.gt(x, maxRest)) x else maxRest } }这里隐式参数
ordering
用于提供关于前面提到的elements
中的类型T
的排序信息。由于调用maxElements
时必须给出elements
,因此编译器在编译时就会知道T
是什么,因此就能确定类型Ordering[T]
的隐式定义是否可用。如果可用,则它就可以隐式的作为ordering
传入第二个参数列表。xxxxxxxxxx
maxElement(List(1,5,7,2)) // 编译器插入针对 Int 的 ordering maxLement(List("one","two","three")) // 编译器插入针对 String 的 ordering从代码风格而言,最好是对隐式参数使用特殊的、定制化的类型。
前面的例子也可以这样写:
xxxxxxxxxx
def maxElement[T](elements: List[T])(implicit ordering: (T, T) => Boolean): T = { ... }这个版本的隐式参数
ordering
的类型为(T, T) => Boolean
,这是一个非常泛化的类型,覆盖了所有从两个T
到Boolean
的函数。这个类型并没有给出任何关于T
类型的信息,它可以是相等性测试、小于等于测试、大于等于测试.... 。而前面的例子:
xxxxxxxxxx
def maxElement[T](elements: List[T])(implicit ordering: Ordering[T]): T = { ... }它用一个类型为
Ordering[T]
的参数ordering
。这个类型中的单词Ordering
明白无误的表达了这个隐式参数的作用:对类型为T
的元素进行排序。由于这个类型更为特殊,因此在标准库中添加相关隐式定义不会带来什么麻烦。与此相反,如果我们在标准库里添加一个类型为
(T,T) => Boolean
的隐式定义,则编译器广泛的自动传播这个定义,这会带来很多稀奇古怪的行为。因此有这样的代码风格规则:在给隐式参数的类型命名时,使用一个能确定其职能的名字。
5.4 上下文界定
当我们使用隐式参数时,编译器不仅会给这个参数提供一个隐式值,它还会将这个参数作为一个可以在方法体中使用的隐式定义。
如前面的例子:
xxxxxxxxxx
def maxElement[T](elements: List[T])(implicit ordering: Ordering[T]): T = { elements match { case List() => throw new IllegalArgumentException("empty list!") case List(x) => x case x :: rest => val maxRest = maxElement(rest) // 这里会隐式添加 (ordering) if(ordering.gt(x, maxRest)) x else maxRest // 这里必须显式给出 } }编译器检查
maxElement(rest)
时发现类型不匹配,由于第二个参数列表是隐式的,因此编译器并不会立即放弃类型检查。它会查找合适类型的隐式参数,在上述代码中这个类型是Ordering[T]
。编译器找到了这样一个隐式参数,并将方法调用重写为maxList(rest)(ordering)
。还有一种方法可以去掉对
ordering
的显式使用。这涉及标准类库中定义的方法:xxxxxxxxxx
def implicitly[T](implicit t: T) = t调用
implicitly[Foo]
的作用是编译器会查找一个类型为Foo
的隐式定义,然后编译器用这个对象来调用implicitly
方法,该方法直接将这个隐式对象返回。因此,如果希望在当前作用域内找到类型为
Foo
的隐式对象,则可以直接写implicitly[Foo]
。采用这个方式之后,上述例子改为:
xxxxxxxxxx
def maxElement[T](elements: List[T])(implicit ordering: Ordering[T]): T = { elements match { case List() => throw new IllegalArgumentException("empty list!") case List(x) => x case x :: rest => val maxRest = maxElement(rest) // 这里会隐式添加 (ordering) if(implicitly[Ordering[T]].gt(x, maxRest)) x else maxRest // 使用 implicitly } }在这个版本中,方法体内没有任何地方提到
ordering
参数。这个模式很常用,因此
Scala
允许我们省掉这个参数并使用上下文界定context bound
来缩短方法签名。采用上下文界定的方式为:xxxxxxxxxx
def maxElement[T : Ordering](elements: List[T]): T = { // 使用上下文界定 elements match { case List() => throw new IllegalArgumentException("empty list!") case List(x) => x case x :: rest => val maxRest = maxElement(rest) // 这里会隐式添加 (ordering) if(implicitly[Ordering[T]].gt(x, maxRest)) x else maxRest // implicitly } }这里
[T: Ordering]
这样的语法是一个上下文界定,它完成两件事:首先,它像平常那样引入一个类型参数
T
其次,它添加了一个类型为
Ordering[T]
的隐式参数。至于这个隐式参数叫什么名字,你完全不需要知道。你只需要知道这个隐式参数的类型,并通过
implicitly[Ordering[T]
获得该隐式参数即可。
直观上讲,可以将上下文界定想象为对类型参数做某种描述:
[T <: Ordering[T]]
表示T
是一个Ordered[T]
类型或者其子类[T : Ordering]
并没有任何关于T
是什么类型的定义,仅仅表示T
带有某种形式的排序。
5.5 多个转换可用
当作用域内存在多个隐式转换可用时,大部分场景
Scala
编译器都会拒绝插入转换。在
Scala 2.8
中对这个规则有所放宽:如果所有可用的转换中,某个转换比其它更为具体more specific
,那么编译器就会选择这个更为具体的转换。如果满足下面任何一条,则我们就说某个隐式转换比另一个转换更为具体:
- 前者的入参类型是后者入参类型的子类型。
- 两者都是方法,而前者所在的类扩展自后者所在的类。
增加这个修改规则的动机是:改进
Java
集合、Scala
集合、字符串之间的互操作。例如在
Scala 2.8
之前:xxxxxxxxxx
"abc".reverse.reverse == "abc"这个表达式结果是
false
。原因是cba
的类型是String
,Scala 2.8
之前String
没有.reverse
操作,因此字符串被转换成了Scala
的集合,而对集合的reverse
返回的是另一个集合,因此表达式左侧返回一个集合,它不等于字符串。在
Scala 2.8
之后,Scala
提供了一个更具体的从String
转换到StringOps
的隐式转换。StringOps
有很多像reverse
这样的方法,不过它们并不返回集合,而是返回字符串。到
StringOps
的隐式转换直接定义在Predef
中。
5.6 调试
- 在调试时,可以将转换显式写出。如果显式写出还出错,你就能很快定位问题;如果显式写出不报错,则说明某个其它规则(如作用域规则)阻止了该隐式转换。
- 可以通过
-Xprint:typer
编译器选项查看编译器插入的隐式转换。 - 如果隐式转换被频繁使用,则会让代码变得难以阅读。因此在添加一个新的隐式转换之前,首先问自己能否通过其它手段达到相似的效果,比如继承、混入或者方法重载。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论