Scala函数与函数式编程

函数是scala的重要组成部分, 本文将探讨scala中函数的应用.程序员

scala做为支持函数式编程的语言, scala能够将函数做为对象即所谓"函数是一等公民".编程

函数定义

scala源文件中能够定义两类函数:数组

  • 类方法: 类声明时定义, 由类实例进行调用安全

  • 局部函数: 在函数内部定义, 做用域只限于定义它的函数内部并发

这里只关注函数定义相关内容, 关于类的有关内容请参考面向对象的相关内容.分布式

scala使用def关键字定义函数:函数式编程

def test() {
  println("Hello World!");
}

由于是静态类型语言, 定义含参数和返回值的函数须要指定类型, 语法略有不一样:函数

def add(x:Int, y:Int): Int = {
  return x + y;
}

scala支持默认参数:单元测试

def add(x:Int = 0, y:Int = 0):Int = {
    return x + y;
}

能够指定最后一个参数为可变参数, 从而接受数目不定的同类型实参:测试

scala> def echo (args: String *) { for (arg <- args) println(arg) }

scala> echo("Hello", "World")
Hello
World

String *类型的参数args其实是一个Array[String]实例, 可是不能将一个Array做为参数传给args.

若需传递Array做为实参,须要使用arr :_*传递实参:

scala> val arr= Array("Hello" , "World")
arr: Array[String] = Array(Hello, World)

scala> echo(arr: _*)
Hello
World

命名参数容许以任意顺序传入参数:

scala> def speed(dist:Double, time:Double):Double = {return dist / time}

scala> speed(time=2.0, dist=12.2)
res28: Double = 6.1

参数传递

scala的参数传递采用传值的方式, 参数被当作常量val而非变量var传入.

当咱们试图编写一个swap函数时,出现错误:

scala> def swap(x:Int, y:Int) {var t = x; x = y; y = t;}
<console>: error: reassignment to val
       def swap(x:Int, y:Int) {var t = x; x = y; y = t;}
                                            ^
<console>: error: reassignment to val
       def swap(x:Int, y:Int) {var t = x; x = y; y = t;}
                                                   ^

scala中的标识符实际是引用而非对象自己, 这一点与Java相同。 类实例中的属性和容器的元素实际上只保存了引用, 并不是将成员自身保存在容器中。

不熟悉Java的同窗能够将对象和引用类比为C中的变量和指针

val将一个对象设为常量, 使得咱们没法修改其中保存的引用,可是容许咱们修改其引用的其它对象.

以二维数组val arr = Array(1,2,3)为例。 由于arr为常量,咱们没法修改arr使其为其它值, 但咱们能够修改arr引用的对象arr(0)使其为其它值:

scala> val arr = Array(1,2,3)
arr: Array[Int] = Array(1, 2, 3)

scala> arr = Array(2,3,4)
<console>:12: error: reassignment to val
       arr = Array(2,3,4)
           ^
scala> arr(0) = 2
arr: Array[Int] = Array(2, 2, 3)

参数传递过程一样知足这个性质:

scala> var arr = Array(1,2,3)
arr: Array[Int] = Array(1, 2, 3)

scala> def fun(arr:Array[Int]):Array[Int] = {arr(0) += 1; return arr;}
fun: (arr: Array[Int])Array[Int]

scala> fun(arr)
res: Array[Int] = Array(3, 2, 3)

scala> arr
arr: Array[Int] = Array(3, 2, 3)

换名传递

上述参数传递采用传值的方式传递: 在函数调用时实参值被传入函数执行过程当中参数值不会由于实参值改变而发生改变。

换名传递则不当即进行参数传递, 只有参数被访问时才会去取实参值, 即形参成为了实参的别名.

换名传递能够用于实现惰性取值的效果.

换名传递参数用: =>代替:声明, 注意空格不能省略.

def work():Int = {
  println("generating data");
  return (System.nanoTime % 1000).toInt
}

def delay(t: => Int) {
  println(t);
  println(t);
}

scala> delay(work())
generating data
247
generating data
143

从结果中能够注意到work()函数被调用了两次, 而且换名参数t的值发生了改变.

换名参数只是传递时机不一样,仍然采用val的方式进行传递.

函数字面量

函数字面量又称为lambda表达式, 使用=>符号定义:

scala> var fun = (x:Int) => x + 1
fun: Int => Int = $$Lambda$1422/1621418276@3815c525

函数字面量是一个对象, 能够做为参数和返回值进行传递.

使用_逐一替换普通函数中的参数 能够获得函数对应的字面量:

scala> def add(x:Int, y:Int):Int = {return x + y}
add: (x: Int, y: Int)Int

scala> var fun = add(_,_)
fun: (Int, Int) => Int = $$Lambda$1423/1561881364@37b117dd

部分应用函数与偏函数

使用_代替函数参数的过程当中,若是只替换部分参数的话则会获得一个新函数, 称为部分应用函数(Partial Applied Function):

scala> val increase = add(_:Int, 1)
increase: Int => Int = $$Lambda$1453/981330853@78fc5eb

偏函数是一个数学概念, 是指对定义域中部分值没有定义返回值的函数:

def pos = (x:Int) => x match {
        case x if x > 0 => 1
}

高阶函数

函数字面量能够做为参数或返回值, 接受函数字面量做为参数的函数称为高阶函数.

scala内置一些高阶函数, 用于定义集合操做:

collection.map(func)将集合中每个元素传入func并将返回值组成一个新的集合做为map函数的返回值:

scala> var arr = Array(1,2,3)
arr: Array[Int] = Array(1, 2, 3)

scala> arr.map(x=>x+1)
res: Array[Int] = Array(2, 3, 4)

上述示例将arr中每一个元素执行了x=>x+1操做, 结果组成了一个新的集合返回.

collection.flatMap(func)相似于map, 只不过func返回一个集合, 它们的并集做为flatMap的返回值:

scala> var arr = Array(1,2,3)
arr: Array[Int] = Array(1, 2, 3)

scala> arr.flatMap(x=>Array(x,-x))
res: Array[Int] = Array(1, -1, 2, -2, 3, -3)

上述示例将arr中每一个元素执行x=>Array(x, -x)获得元素自己和它相反数组成的数组,最终获得全部元素及其相反数组成的数组.

collection.reduce(func)中的func接受两个参数, 首先将集合中的两个参数传入func,获得的返回值做为一个参数和另外一个元素再次传入func, 直处处理完整个集合.

scala> var arr = Array(1,2,3)
arr: Array[Int] = Array(1, 2, 3)

scala> arr.reduce((x,y)=>x+y)
res: Int = 6

上述示例使用reduce实现了集合求值. 实际上, reduce并不保证遍历的顺序, 若要求特定顺序请使用reduceLeftreduceRight.

zip函数虽然不是高阶函数,可是常和上述函数配合使用, 这里顺带一提:

scala> var arr1 = Array(1,2,3)
arr1: Array[Int] = Array(1, 2, 3)

scala> var arr2 = Array('a', 'b', 'c')
arr2: Array[Char] = Array(a, b, c)

scala> arr1.zip(arr2)
res: Array[(Int, Char)] = Array((1,a), (2,b), (3,c))

高阶函数其实是自定义了控制结构:

scala> def twice(func: Int=>Int, x: Int):Int = func(func(x))
twice: (func: Int => Int, x: Int)Int

scala> twice(x=>x*x, 2)
res: Int = 16

twice函数定义了将函数调用两次的控制结构, 所以实参2被应用了两次x=>x*x获得16.

柯里化

函数的柯里化(currying)是指将一个接受n个参数的函数变成n个接受一个参数的函数.

以接受两个参数的函数为例,第一个函数接受一个参数 并返回一个接受一个参数的函数.

原函数:

scala> def add(x:Int, y:Int):Int = {return x+y}
add: (x: Int, y: Int)Int

进行柯里化:

scala> def add(x:Int)= (y:Int)=>x*y
add: (x: Int)Int => Int

这里没有指明返回值类型, 交由scala的类型推断来决定. 调用柯里化函数:

scala> add(2)(3)
res10: Int = 6

scala> add(2)
res11: Int => Int = $$Lambda$1343/1711349692@51a65f56

能够注意到add(2)返回的还是函数.

scala提供了柯里化函数的简化写法:

scala> def add(x:Int)(y:Int)={x+y}
add: (x: Int)(y: Int)Int

本文介绍了一些关于scala函数式编程(functional programming, FP)的特性, 在这里简单介绍一下函数式编程范式.

函数式编程中, 函数是从参数到返回值的映射而非带有返回值的子程序; 变量(常量)也只是一个量的别名而非内存中的存储单元.

也就是说函数式编程关心从输入到输出的映射, 不关心具体执行过程. 好比使用map对集合中的每一个元素进行操做, 可使用for循环进行迭代, 也能够将元素分发到多个worker进程中处理.

函数式编程可理解为将函数(映射)组合为大的函数, 最终整个程序即为一个函数(映射). 只要将数据输入程序, 程序就会将其映射为结果.

这种设计理念须要知足两个特性. 一是高阶函数, 它容许函数进行复合; 另外一个是函数的引用透明性, 它使得结果不依赖于具体执行步骤只依赖于映射关系.

结果只依赖输入不依赖上下文的特性称为引用透明性; 函数对外部变量的修改被称为反作用.只经过参数和返回值与外界交互的函数称为纯函数,纯函数拥有引用透明性和无反作用性.

不可变对象并不是必须, 但使用不可变对象能够强制函数不修改上下文. 从而避免包括线程安全在内不少问题.

函数式编程的特性使得它拥有不少优点:

  • 函数结果只依赖输入不依赖于上下文, 使得每一个函数都是一个高度独立的单元, 便于进行单元测试和除错.

  • 函数结果不依赖于上下文也不修改上下文, 从而在并发编程中不须要考虑线程安全问题, 也就避免了线程安全问题带来的风险和开销. 这一特性使得函数式程序很容易部署于并行计算和分布式计算平台上.

函数式编程在不少技术社区都是有着普遍争议的话题, 笔者认为"什么是函数编程","函数式编程的精髓是什么"这类问题并不重要。

做为程序员应该考虑的是"函数式编程适合解决什么问题?它有何有缺?"以及"什么时候适合应用函数式编程?这个问题中如何应用函数式编程?".

函数式编程并不是"函数式语言"的专利. 目前包括Java,Python在内的, 愈来愈多的语言开始支持函数式特性, 咱们一样能够在Java或Python项目上发挥函数式编程的长处.

相关文章
相关标签/搜索