0%

Scala学习笔记

Scala 学习笔记

Scala 函数柯里化(Currying)

柯里化(currying)是指将原来接收两个参数的函数变成新的接收一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。

实例

首先我们定义一个函数

1
def add(x:Int,y:Int)=x+y

那么我们应用的时候,应该是这样用:add(1,2)

现在我们把这个函数变一下形:

1
def add(x:Int)(y:Int)=x+y

那么我们应用的时候应该这样用:add(1)(2)最后结果一样都是3,这种方程(过程)就叫错柯里化(currying)

实现过程

add(1)(2)实际上是依次调用两个普通函数(非柯里化函数),第一次调用使用一个c桉树x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。

实际上最先演变成这样一个方法:

1
def add(x:Int)=(y:Int)=>x+y

那么这个函数是什么意思呢? 接收一个x为参数,返回一个匿名函数,该匿名函数的定义是:接收一个Int型参数y,函数体为x+y。现在我们来对这个方法进行调用。

1
val result = add(1)

返回一个result,那result的值应该是一个匿名函数:(y:Int)=>1+y

所以为了得到结果,我们继续调用result。

1
val sum = result(2)

最后打印出来的结果就是3。

implicits

在 Scala 中的 implicit 定义指编译器在需要修复类型匹配时可以用来自动插入的定义。比如说,如果 x+y 类型不匹配,那么编译器可能试着使用 convert(x) + y, 其中 convert 由某个 implicit 定义的,这有点类似一个整数和一个浮点数相加,编译器可以自动把整数转换为浮点数。Scala 的 implicit 定义是对这种情况的一个推广,你可以定义一个类型在需要时,如何自动转换成另外一种类型。

标记规则

只有哪些使用 implicit 关键字的定义才是可以使用的隐式定义。关键字 implicit 用来标记一个隐式定义。编译器才可以选择它作为隐式变化的候选项。你可以使用 implicit 来标记任意变量,函数或是对象。

例如下面为一个隐式函数定义:

1
implicit def intToString(x:Int) : x.toString

编译器只有在 convert 被标记成 implicit 才会将 x + y 改成convert(x) + y 。当然这是在 x + y 类型不匹配时。

范围选择

编译器在选择备选 implicit 定义时,只会选取当前作用域的定义,比如说编译器不会去调用 someVariable.convert。如果你需要使用 someVariable.convert,你必须把 someVarible 引入到当前作用域。也就是说编译器在选择备选 implicit 时,只有当 convert 是当前作用域下单个标志符时才会作为备选 implicit。比如说,对于一个函数库来说,在一个 Preamble 对象中定义一些常用的隐式类型转换非常常见,因此需要使用 Preamble 的代码可以使用 “import Preamble._” 把这些 implicit 定义引入到当前作用域才可以。

这个规则有一个例外,编译器也会在类的伙伴对象定义中查找所需的 implicit 定义。例如下面的定义:

1
2
3
4
5
6
7
object Dollar {
implicit def dollarToEuro(x:Dollar):Euro = ...
...
}
class Dollar {
...
}

如果在 class Dollar 的方法有需要 Euro 类型,但输入数据使用的是 Dollar,编译器会在其伙伴对象 object Dollar 查找所需的隐式类型转换,本例定义一个从 Dollar 到 Euro 的 implicit 定义可以使用。

一次规则

编译器在需要使用 implicit 定义时,只会试图转换一次,也就是编译器永远不会把 x + y 改写成 convert1(convert2(x)) + y。

优先规则

编译器不会在 x+y 已经是合法的情况下去调用 implicit 规则。

命名规则

你可以为implicit定义任意的名称。通常情况下可以任意命名,implicit的名称只在两种情况下有用:一是你想在一个方法中明确指出,另外一个是想把那一个引入到当前作用域;比如我们定义一个对象,包含两个 implicit定义:

1
2
3
4
object MyConversions {
implicit def stringWrapper(s:String):IndexedSeq[Char] = ...
implicit def intToString(x:Int):String = ...
}

在你的应用中,你想使用 stringWrapper 变换,而不想把整数自动转换成字符串,你可以只引入 stringWrapper。

1
import  MyConversions.stringWrapper

编译器使用 implicit 的几种情况

有三种情况使用 implicit: 一是转换成预期的数据类型,二是转换 selection 的 receiver,三是隐含参数。转换成预期的数据类型比如你有一个方法参数类型是 IndexedSeq[Char],在你传入 String 时,编译器发现类型不匹配,就检查当前作用域是否有从 String 到 IndexedSeq 隐式转换。

转换 selection 的 receiver 允许你适应某些方法调用,比如 “abc”.exist ,”abc”类型为 String,本身没有定义 exist 方法,这时编辑器就检查当前作用域内 String 的隐式转换后的类型是否有 exist 方法,发现 stringWrapper 转换后成 IndexedSeq 类型后,可以有 exist 方法,这个和 C# 静态扩展方法功能类似。

隐含参数有点类似是缺省参数,如果在调用方法时没有提供某个参数,编译器会查找当前作用域是否有符合条件的 implicit 对象作为参数传入(有点类似 dependency injection)。

广义约束类型(Generalized type constraints)

参考:http://blog.bruchez.name/2015/11/generalized-type-constraints-in-scala.html

1
Type1 <:< Type2

Make sure that Type1 is a subtype of Type2, or else report an error.

1
Type1 =:= Type2

Make sure that Type1 is exactly the same as Type2, or else report an error.

1
T >: U

“type T is a supertype of type U” or “type T has type U as lower bound”

1
T <: U

“type T is a subtype of type U” or “type T has type U as upper bound”

apply方法

apply方法是对“类名()”的重载,当类或对象有一个主要用途时apply方法提供了很好的语法糖