文章目录
作用
Scala 中可以让函数库调用变得更加方便的隐式变换和隐式参数,以及如何通过它们来避免一些繁琐和显而易见的细节问题。 内容主要包括 implicits 的使用规则、隐含类型转换、转换被方法调用的对象等
解决什么问题
应用中自己写的代码和调用的第三方函数库有着一个基本的区别:也就是你可以任意修改和扩展自己写的代码。而一般来说,在没有源码的情况下,你很难扩展第三方函数库,函数库提供了什么就只能利用什么。
C 3.0 支持静态扩展方法,可以为已经定义的库、类进行扩展。
在 Scala 中,解决这个问题的途径是使用隐含类型变换和隐式参数。它们可以让函数库的调用变得更加方便,并避免一些繁琐和显而易见的细节问题。
Scala 的 implicit 可以有 implicit 类、方法和参数。
本项目课的几个实验将为你介绍 Scala 的隐式变换和隐式参数的用途。
使用 implicits 的一些规则
在 Scala 中的 implicit 定义,是指编译器在需要修复类型匹配时,可以用来自动插入的定义。比如说,如果 x + y 类型不匹配,那么编译器可能尝试使用 convert(x) + y, 其中 convert 就是由某个 implicit 定义的。
这有点类似于一个整数和一个浮点数相加,编译器可以自动把整数转换为浮点数。Scala 的 implicit 定义是对这种情况的一个推广,你可以定义一个类型,使其在需要时自动转换成另外一种类型。
Scala 的 implicit 定义符合下面一些规则:
3.1.1 标记规则
只有那些使用 implicit 关键字的定义才是可以使用的隐式定义。关键字 implicit 用来标记一个隐式定义。编译器随之可以选择它作为隐式变化的候选项。你可以使用 implicit 来标记任意变量,函数或是对象。
例如下面为一个隐式函数定义,尝试在Shell中输入这个语句:
implicit def intToString(x:Int) : x.toString
编译器只有在 convert 被标记成 implicit 后,才会将 x + y 改成 convert(x) + y。当然这是在 x + y 类型不匹配时。
3.1.2 范围规则
编译器在选择备选 implicit 定义时,只会选取当前作用域的定义。比如说,编译器不会去调用 someVariable.convert 。如果你需要使用 someVariable.convert ,你必须把 someVarible 引入到当前作用域。也就是说,编译器在选择备选 implicit 时,只有当 convert 是当前作用域下的单个标志符时,它才会作为备选 implicit 。举个例子,对于一个函数库而言,在一个 Preamble 对象中定义一些常用的隐式类型转换非常常见,因此需要使用 Preamble 的代码时,可以通过 “import Preamble._`” ,从而把这些 implicit 定义引入到当前作用域。
这个规则有一个例外,编译器也会在类的伙伴对象定义中查找所需的 implicit 定义。例如下面的定义:
object Dollar {
implicit def dollarToEuro(x:Dollar):Euro = ...
...
}
class Dollar {
...
}
你可以尝试补充上述代码中的细节,并在 Shell 中验证一下。
如果在 class Dollar 的方法有需要 Euro 类型,但输入数据使用的是 Dollar ,编译器会在其伙伴对象 object Dollar 查找所需的隐式类型转换,本例定义了一个从 Dollar 到 Euro 的 implicit 定义以供使用。
3.1.3 一次规则
编译器在需要使用 implicit 定义时,只会试图转换一次,也就是编译器永远不会把 x + y 改写成 convert1(convert2(x)) + y 。
3.1.4 优先规则
编译器不会在 x+y 已经是合法的情况下去调用 implicit 规则。
3.1.5 命名规则
你可以为 implicit 定义任意的名称。通常情况下,你可以任意命名, implicit 的名称只在两种情况下有用:一是你想在一个方法中明确指明;另外一个是想把那一个引入到当前作用域。
比如,我们定义一个对象,包含两个 implicit 定义:
object MyConversions {
implicit def stringWrapper(s:String):IndexedSeq[Char] = ...
implicit def intToString(x:Int):String = ...
}
请尝试补充代码中的细节部分,并在 Shell 中输入后验证一下。
在你的应用中,你想使用 stringWrapper 变换,而不想把整数自动转换成字符串,你可以只引入 stringWrapper 。
用法如下:
import MyConversions.stringWrapper
3.1.6 编译器使用 implicit 的几种情况
有三种情况使用 implicit :
转换成预期的数据类型:比如你有一个方法参数类型是 IndexedSeq[Char] ,在你传入 String 时,编译器发现类型不匹配,就会检查当前作用域是否有从 String 到 IndexedSeq 隐式转换。
转换 selection 的 receiver :这种情况下,允许你适应某些方法调用。比如 “abc”.exist ,“abc” 的类型为 String ,它本身没有定义 exist 方法,这时编辑器就检查当前作用域内 String 的隐式转换后的类型是否有 exist 方法。若发现 stringWrapper 转换后成 IndexedSeq 类型后,就会有 exist 方法,这个与 C 静态扩展方法功能类似。
隐含参数:隐含参数有点类似于缺省参数。如果在调用方法时没有提供某个参数,编译器会查找当前作用域是否有符合条件的 implicit 对象作为参数传入(有点类似 dependency injection )。
3.2 隐含类型转换
使用隐含转换将变量转换成预期的类型,是编译器最先使用 implicit 的地方。这个规则非常简单,当编译器看到类型 X 而却需要类型 Y ,它就在当前作用域查找是否定义了从类型 X 到类型 Y 的 隐式定义 。
比如,通常情况下,双精度实数不能直接当整数使用,因为会损失精度。你可以尝试在 Shell 中输入下面的语句来验证:
scala> val i:Int = 3.5
:7: error: type mismatch;
found : Double(3.5)
required: Int
val i:Int = 3.5
^
当然,你可以直接调用 3.5.toInt 。
这里我们定义一个从 Double 到 Int 的隐含类型转换的定义,然后再把 3.5 赋值给整数,这样就不会报错了。
尝试在 Shell 中输入下面的语句,然后再次验证一下:
scala> implicit def doubleToInt(x:Double) = x toInt
doubleToInt: (x: Double)Int
scala> val i:Int = 3.5
i: Int = 3
此时编译器看到一个浮点数 3.5 ,而当前赋值语句需要一个整数。此时,按照一般情况,编译器会报错。但在报错之前,编译器会搜寻是否定义了从 Double 到 Int 的隐含类型转换。在本例中,它找到了一个 doubleToInt 。因此编译器将把 val i:Int = 3.5
转换成 val i:Int = doubleToInt(3.5) 。
这就是一个隐含转换的例子。但是从浮点数自动转换成整数并不是一个好的例子,因为会损失精度。 Scala 在需要时会自动把整数转换成双精度实数,这是因为在 Scala.Predef 对象中定义了一个隐式转换:
implicit def int2double(x:Int) :Double = x.toDouble
而 Scala.Predef 是自动引入到当前作用域的,因此编译器在需要时,会自动把整数转换成 Double 类型。
3.3 转换被方法调用的对象
隐式变换也可以转换调用方法的对象。若编译器看到 X.method ,而类型 X 没有定义 method (包括基类)方法,那么编译器就查找作用域内定义的从 X 到其它对象的类型转换。而对于类型 Y ,若它定义了 method 方法,编译器就首先使用隐含类型转换把 X 转换成 Y ,然后调用 Y 的 method 。
下面我们看看这种用法的两个典型用法:
3.3.1 支持新的类型
这里我们使用课程《 Scala 开发教程》中讲解 Ordered Trait 时,定义的 Rational 类型为例。先来回顾一下其定义:
class Rational (n:Int, d:Int) {
require(d!=0)
private val g =gcd (n.abs,d.abs)
val numer =n/g
val denom =d/g
override def toString = numer + “/” +denom
def +(that:Rational) =
new Rational(
numer * that.denom + that.numer* denom,
denom * that.d

Scala的隐式转换和参数使得代码更加简洁,允许库函数扩展。隐式转换在类型不匹配时自动进行,如从Int到Double。规则包括标记、范围、一次、优先和命名。隐式参数类似于默认参数,当未提供时,编译器寻找合适的隐式对象。实验展示了如何通过隐式转换支持新的类型和模拟新语法结构,例如将整数转换为Rational类型。隐式转换和参数是Scala中增强代码灵活性的关键工具。
最低0.47元/天 解锁文章
4080

被折叠的 条评论
为什么被折叠?



