First Class Functions
函数式编程的核心就是函数应当是首类的。首类表示函数不仅能得到声明和调用,还可以作为一个数据类型用在这个语言的任何地方。
首类函数与其他数据类型一样,可以采用字面量创建;或者存储在值、变量、或数据结构等容器中;还可以作为一个函数的参数或返回值。
如果一个函数接收其他函数作为参数或使用函数作为返回值,就称为高阶函数(higher-order function)。比如map和reduce。map取一个函数参数,用它处理一个或多个项转换为新值。reduce取一个函数参数,将包含多项的集合规约为一项。
函数类型和值
函数类型
函数类型就是输入与输出的简单组合
格式
代码语言:javascript复制([<type>, ...]) => <type>
scala> def double(x: Int): Int = x * 2
double: (x: Int)Int
// 只有单个参数的函数可以省略小括号
scala> val myDouble: (Int) => Int = double (1)
myDouble: Int => Int = <function1>
scala> myDouble(5) (2)
res1: Int = 10
在上面这个例子中,定义了一个函数,然后赋值给一个identifier “myDouble“。这个myDouble是一个函数值了,可以调用刚刚的函数。
与定义value的格式是一样的:val myDouble: 参数类型 = double。这里的参数类型就是函数类型。
用通配符为函数赋值
通配符下划线相当于占位符,表示将来的一个函数调用。要么使用显式的类型,要么使用通配符_定义函数值以及用函数赋值。才能区分这是个函数值function value(存入函数的value)还是函数调用。
格式
代码语言:javascript复制val <identifier> = <function name> _
scala> def double(x: Int): Int = x * 2
double: (x: Int)Int
// 与上面val myDouble: (Int) => Int = double 是一致的
scala> val myDouble = double _
myDouble: Int => Int = <function1>
scala> val amount = myDouble(20)
amount: Int = 40
scala> def max(a: Int, b: Int) = if (a > b) a else b
max: (a: Int, b: Int)Int
scala> val maximize = max _
maximize: (Int, Int) => Int = $$Lambda$1199/2029885969@d0993f0
scala> maximize(20,30)
res9: Int = 30
以上介绍了如何指定函数type以及把函数保存在值中
高阶函数
高阶函数也是函数,包含一个函数类型的值作为输入或返回值
代码语言:javascript复制scala> def safeStringOp(s: String, f: String => String) = {
| if (s != null) f(s) else s
| }
safeStringOp: (s: String, f: String => String)String
scala> def reverser(s: String) = s.reverse
reverser: (s: String)String
scala> safeStringOp(null, reverser)
res4: String = null
scala> safeStringOp("Ready", reverser)
res5: String = ydaeR
函数字面量
代码语言:javascript复制scala> val doubler = (x: Int) => x * 2
doubler: Int => Int = <function1>
scala> val doubled = doubler(22)
doubled: Int = 44
上面这个例子中,函数字面量为(x:Int) => x*2,定义了一个有类型的输入和函数体。
以下是等价的两种函数定义方法:定义函数并赋值到一个函数值;函数字面量重新定义。
代码语言:javascript复制scala> val maximize: (Int, Int) => Int = max (2)
maximize: (Int, Int) => Int = <function2>
scala> val maximize = (a: Int, b: Int) => if (a > b) a else b (3)
maximize: (Int, Int) => Int = <function2>
scala> val start = () => "=" * 50 "nStarting NOWn" "=" * 50
start: () => String = <function0>
scala> println( start() )
===================================================
Starting NOW
===================================================
以下stringOp接收一个函数值参数,可以接收一个函数字面量。
代码语言:javascript复制scala> def safeStringOp(s: String, f: String => String) = {
| if (s != null) f(s) else s
| }
safeStringOp: (s: String, f: String => String)String
scala> safeStringOp(null, (s: String) => s.reverse)
res7: String = null
事实上,f的类型定义完之后,可以从函数字面量中删除显式类型。不用写(s: String)
代码语言:javascript复制scala> safeStringOp("Ready", s => s.reverse)
res10: String = ydaeR
占位符语法
Placeholder Syntax占位符语法是函数字面量。将命名参数替换为通配符“_"。可以在以下情况使用:
- 函数的显式类型在字面量之外指定
- 参数最多只用1次
scala> val doubler: Int => Int = _ * 2
doubler: Int => Int = <function1>
scala> def safeStringOp(s: String, f: String => String) = {
| if (s != null) f(s) else s
| }
safeStringOp: (s: String, f: String => String)String
// 等价于一个字面量s => s.reverse
scala> safeStringOp(null, _.reverse)
res11: String = null
如果有多个占位符的话,按位置替换输入参数
代码语言:javascript复制scala> def combination(x: Int, y: Int, f: (Int,Int) => Int) = f(x,y)
combination: (x: Int, y: Int, f: (Int, Int) => Int)Int
scala> combination(23, 12, _ * _)
res13: Int = 276
使用两个类型参数(表示两个类型(可以不同也可以相同),但是可以代表任何具体类型int,double,boolean......)
代码语言:javascript复制scala> def tripleOp[A,B](a: A, b: A, c: A, f: (A, A, A) => B) = f(a,b,c)
tripleOp: [A, B](a: A, b: A, c: A, f: (A, A, A) => B)B
scala> tripleOp[Int,Int](23, 92, 14, _ * _ _)
res15: Int = 2130
scala> tripleOp[Int,Double](23, 92, 14, 1.0 * _ / _ / _)
res16: Double = 0.017857142857142856
部分应用一个函数和柯里化
partially apply 这个函数:表示重用一个函数调用,而且希望保留一些参数不想再次输入。
代码语言:javascript复制scala> def factorOf(x: Int, y: Int) = y % x == 0
factorOf: (x: Int, y: Int)Boolean
scala> val f = factorOf _
f: (Int, Int) => Boolean = <function2>
scala> val x = f(7, 20)
x: Boolean = false
// 使用通配符替代其中一个参数,后面这个:Int可以不加。
scala> val multipleOf3 = factorOf(3, _: Int)
multipleOf3: Int => Boolean = <function1>
scala> val y = multipleOf3(78)
y: Boolean = true
另外一种partially apply的方法是使用有多个参数表的函数。apply一个参数表中的参数二另一个不应用,方便区分。这种技术叫柯里化currying
代码语言:javascript复制scala> def factorOf(x: Int)(y: Int) = y % x == 0
factorOf: (x: Int)(y: Int)Boolean
scala> val isEven = factorOf(2) _
isEven: Int => Boolean = <function1>
scala> val z = isEven(32)
z: Boolean = true
传名参数
by-name parameter
这个参数可以传进去一个值也可以传进去一个参数。
但要注意函数传入到这个传名参数是,一定要明明白白反复调用这个函数参数可能带来的开销。
格式
代码语言:javascript复制<identifier>: => <type>
// 函数字面量是 <identifier>:<type> => <type>
// 值是 <identifier>: <type>
scala> def doubles(x: => Int) = {
| println("Now doubling " x)
| x * 2
| }
// 可传一个值
scala> doubles(5)
Now doubling 5
res18: Int = 10
scala> def f(i: Int) = { println(s"Hello from f($i)"); i }
f: (i: Int)Int
// 也可以传一个函数。doubles函数调用了x两次,调用了这个传名参数两次。
scala> doubles( f(8) )
Hello from f(8)
Now doubling 8
Hello from f(8)
res19: Int = 16
偏函数
Partial Functions
偏函数可以对输入应用一系列case模式的函数字面量,要求输入至少与给定的pattern之一匹配。如果不匹配,抛出一个错误。
用函数字面量块调用高阶函数
用一个表达式块调用工具函数。我们传入了一个值参数uuid,还传入了一个多行函数字面量。(函数字面量就是s=>输出)
代码语言:javascript复制scala> val timedUUID = safeStringOp(uuid, { s =>
| val now = System.currentTimeMillis (2)
| val timed = s.take(24) now (3)
| timed.toUpperCase
| })
timedUUID: String = BFE1DDDA-92F6-4C7A-8BFC-1394546043987
将safeStringOp中的参数分为两个单独的参数组。第二个参数组可以用表达式块语法来调用(第四章 用表达式块调用函数 表达式块计算一个输入)
代码语言:javascript复制scala> def safeStringOp(s: String)(f: String => String) = {
| if (s != null) f(s) else s
| }
safeStringOp: (s: String)(f: String => String)String
scala> val timedUUID = safeStringOp(uuid) { s =>
| val now = System.currentTimeMillis
| val timed = s.take(24) now
| timed.toUpperCase
| }
timedUUID: String = BFE1DDDA-92F6-4C7A-8BFC-1394546915011
来看一个例子,它用到了传名参数,利用类型参数作为这个传名参数(可输入函数或值)的返回类型以及主函数的返回类型。
代码语言:javascript复制// timer():A= 表示timer主函数的返回类型
scala> def timer[A](f: => A): A = { (1)
| def now = System.currentTimeMillis (2)
| val start = now; val a = f; val end = now
| println(s"Executed in ${end - start} ms")
| a
| }
timer: [A](f: => A)A
// 用一个表达式块来调用函数,表达式块里是一个函数字面量,作为输入
scala> val veryRandomAmount = timer { (3)
| util.Random.setSeed(System.currentTimeMillis)
| for (i <- 1 to 100000) util.Random.nextDouble (4)
| util.Random.nextDouble
| }
Executed in 13 ms
veryRandomAmount: Double = 0.5070558765221892