在Scala中,函数是一等公民。究竟什么是函数?
数学上我们经常这样来定义函数 :
y = f(x)
其中,f为函数名称,x为输入,y为输出。x输入函数f之后,经过逻辑运算得到结果y。
def addOne(m: Int): Int = m + 1
val three = addOne(2)
如果函数没有参数,调用的时候可以省略括号:
def three() = 1 + 2 //> three: ()Int
three() //> res0: Int = 3
three //> res1: Int = 3
如果函数的逻辑比较复杂,可以使用{}括起来:
def timesTwo(i: Int): Int = {
println("hello world")
i * 2
}
(x: Int) => x + 1 //> res0: Int => Int = <function1>
可以将匿名函数付给某个val:
val addOne = (x: Int) => x + 1 //> addOne : Int => Int = <function1>
addOne(1) //> res0: Int = 2
匿名函数经常作为其他函数的参数,在调用的时候传给其他函数,例如filter,map。
匿名方法也一样可以用{}括起来:
{ i: Int =>
println("hello world")
i * 2
}
在scala中,函数(function)实际上是Trait的一个实例。
a function that takes one argument is an instance of a Function1 trait
如果函数有一个输入参数,则这个函数是Function1这个Trait的一个实例。通过scala的apply魔力,使得我们在调用对象时感觉像是在调用函数。例如我们定义一个加一函数:
object addOne extends Function1[Int , Int] {
def apply( old : Int) = old +1
}
根据apply的规则,当我们调用addOne(1)的时候,等同与将1传入到addOne对象的apply方法,因此经过运算后得到2:
addOne(1) //> res0: Int = 2
如果通过class来实现:
class addOne extends Function1[Int , Int] {
def apply( old : Int):Int = old +1
}
val addOne = new addOne() //> addOne : com.scala.scala_tutorial.addOne = <function1>
addOne(1) //> res0: Int = 2
函数就是通过这种方式来实现的,apply这个语法糖使得原来面向对象的东西看起来像函数,但是本质上,函数仍然只是Trait的一个实例。
scala中定义了Function1~Function22这个22个Trait,至于为什么是22,不得而知。在实现这些Trait的时候,通过 extends FunctionN是一种方式,但是可以使用更简单的形式:
class addOne extends (Int => Int){
def apply(old : Int): Int = old +1
}
当我们定义匿名函数时:
(x : Int) => x * 2 //> res1: Int => Int = <function1>
(x : Int)实际上作为aply方法的参数,而右半边的则作为方法的实现。实际内部运行机制应该是这样的,首先有一个实现Function1的对象(class也可以):
object doubleFun extends Function1[Int , Int] {
def apply( x: Int) =x * 2
}
如果我们将匿名函数赋给一个值:
val doubleFun = (x : Int) => x * 2
此时这里的值就等同于doubleFun这个对象,调用的时候完全一致:
doubleFun( 2 )
总之,函数是对象。(Functions are objects)!
在数学中,如果一个函数有2个自变量(输入),当我们固定一个时,就只有一个自变量。
z = f(x,y)=x+y z' = f(0,y)=y
类似地,在scala中,如果我们给一个多参数的函数中的某些参数赋值,其他部分不赋值(使用下划线),此时被部分赋值的函数就成为了另一个函数:
def adder(m: Int, n: Int) = m + n //> adder: (m: Int, n: Int)Int
val add2 = adder(2, _: Int) //> add2 : Int => Int = <function1>
我们可以选择函数中任意多个函数进行partial application。(通俗地说,就是固定任意多个自变量)
类似于Java中使用…类表示方法的变长参数,scala使用号来表示变长参数,在正则表达式中表示出现0次或者任务多次。
def capitalizeAll(args: String*) = {
args.map { arg =>
arg.capitalize
}
}
capitalizeAll("hello") //> res0: Seq[String] = ArrayBuffer(Hello)
capitalizeAll("hello","world") //> res1: Seq[String] = ArrayBuffer(Hello, World)
柯里化,引用维基的定义:
是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术
对于下面的函数:
def multiply(m: Int)(n: Int): Int = m * n
我们可以直接调用:
multiply(3)(2) //> res0: Int = 6
或者我们可以使用partial application,先固定某个参数,返回一个参数变少的函数,再调用该函数:
val timesTwo = multiply(2) _ //> timesTwo : Int => Int = <function1>
timesTwo(3) //> res1: Int = 6
柯里化就是要把接收多个参数的函数,变成先接受一个参数,然后返回该参数固定后的函数:
def multi(m:Int , n:Int) :Int = m * n //> multi: (m: Int, n: Int)Int
val carriedMul =( multi _).curried //> carriedMul : Int => (Int => Int) = <function1>
val timesTwo = carriedMul(2) //> timesTwo : Int => Int = <function1>
timesTwo(3) //> res0: Int = 6
上面的例子中,multi函数原本接收两个参数m和n,通过柯里化(调用.curried)之后,变成了只接受一个参数的carriedMul函数,调用柯里化后的carriedMul,返回的是timesTwo函数。
柯里化使得我们可以先对函数应用一个参数,然后稍后再继续应用其他参数。
这两者经常不做区分,例如我们认为方法是可以访问类实例状态的函数。下面例子演示两者的微妙差别:
class C {
var acc = 0
def minc = { acc += 1 }
val finc = { () => acc += 1 }
}
val c = new C //> c : test.C = test$C@43e47e37
c.minc // 调用minc方法
c.finc // 返回值为一个函数 > res0: () => Unit = <function0>