函数是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项目上发挥函数式编程的长处.

Scala函数与函数式编程的更多相关文章

  1. (数据科学学习手札48)Scala中的函数式编程

    一.简介 Scala作为一门函数式编程与面向对象完美结合的语言,函数式编程部分也有其独到之处,本文就将针对Scala中关于函数式编程的一些常用基本内容进行介绍: 二.在Scala中定义函数 2.1 定 ...

  2. Python核心编程读笔 10:函数和函数式编程

    第11章 函数和函数式编程 一 调用函数  1 关键字参数 def foo(x): foo_suite # presumably does some processing with 'x' 标准调用 ...

  3. Python之路Python作用域、匿名函数、函数式编程、map函数、filter函数、reduce函数

    Python之路Python作用域.匿名函数.函数式编程.map函数.filter函数.reduce函数 一.作用域 return 可以返回任意值例子 def test1(): print(" ...

  4. Scala基础篇-函数式编程的重要特性

    1.纯函数 表示函数无副作用(状态变化). 2.引用透明性 表示对相同输入,总是得到相同输出. 3.函数是一等公民 函数与变量.对象.类是同一等级.表示可以把函数当做参数传入另一个函数,或者作为函数的 ...

  5. 跟着ALEX 学python day3集合 文件操作 函数和函数式编程 内置函数

    声明 : 文档内容学习于 http://www.cnblogs.com/xiaozhiqi/  一. 集合 集合是一个无序的,不重复的数据组合,主要作用如下 1.去重 把一个列表变成集合 ,就自动去重 ...

  6. python学习7—函数定义、参数、递归、作用域、匿名函数以及函数式编程

    python学习7—函数定义.参数.递归.作用域.匿名函数以及函数式编程 1. 函数定义 def test(x) # discription y = 2 * x return y 返回一个值,则返回原 ...

  7. day16_函数作用域_匿名函数_函数式编程_map_reduce_filter_(部分)内置函数

    20180729    补充部分代码 20180727    上传代码 #!/usr/bin/env python # -*- coding:utf-8 -*- # ***************** ...

  8. 函数与函数式编程(生成器 && 列表解析 && map函数 && filter函数)-(四)

    在学习python的过程中,无意中看到了函数式编程.在了解的过程中,明白了函数与函数式的区别,函数式编程的几种方式. 函数定义:函数是逻辑结构化和过程化的一种编程方法. 过程定义:过程就是简单特殊没有 ...

  9. day03 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...

随机推荐

  1. python2/3 利用psycopg2 连接postgreSQL数据库。

    psycopg2 是一个通过python连接postgreSQL的库, 不要被它的名称蒙蔽了,你可能发现它的版本是psyconpg2.7.*, 以为它只能在python2上使用,实际上,这只是一个巧合 ...

  2. 开启hadoop集群

    首先开启zookeeper zkServer.sh start start-all

  3. 渗透测试的理论部分2——OSSTMM的详细描述

    昨天休息了一天,今天我要连更两篇博客,作为补充,以下为正文 本章详细描述了OSSTMM内的RAV得分这一理论概念,对日后从事正规安全工作至关重要 OSSTMM为开源安全测试方法论,对OSSTMM不了解 ...

  4. Python12/11--盒子的显隐/布局/z-index/流式布局思想

    1.盒子的显隐 display:none      在页面中不占位,采用定位布局后,显示隐藏都不会影响其他标签,不需要用动画处理时,一般用这个 opacoity : 0        在页面中占位,采 ...

  5. python基本数据类型之字符串(三)

    python基本数据类型之字符串(三) 转换和判断方法 在python中,有一些内置方法可以将字符串转化特定形式,而与之对应的一些方法可以判断字符串是否符合某些形式.因此,在这篇文章中,笔者把转换方法 ...

  6. Python之路(一)-python简介

    一.python简介,python2.x与python3.x的区别 Python是著名的“龟叔”Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言. Py ...

  7. B - Dropping tests

    In a certain course, you take n tests. If you get ai out of bi questions correct on test i, your cum ...

  8. MySQL—函数大全

    一.数学函数: #ABS 绝对值函数 ) ; #BIN 返回二进制,OCT()八进制,hex十六进制 ); #ceiling 天花板整数,也就是大于x的整数 select CEILING(-13.5) ...

  9. MFC控件的颜色设置

    在绘制控件颜色时,控件会发送WM_CTLCOLOR消息给父窗口,父窗口收到消息后,映射到OnCtlColor()函数中处理. 该函数返回一个画刷用于设置子控件的背景颜色,子控件再执行自己的CtlCol ...

  10. 单源最短路径算法——Bellman-ford算法和Dijkstra算法

     BellMan-ford算法描述 1.初始化:将除源点外的所有顶点的最短距离估计值 dist[v] ← +∞, dist[s] ←0; 2.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V ...