函数是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. 在excel中如何利用vba通过网址读取网页title(网址是https的)?

    昨天在百度知道上提了这个问题,我保存了些百度知道我回答的网址,想利用excel直接读取出网址的title,请问vba代码怎么写?(要支持https的) excel大神帮我回答了,在这记录下: Func ...

  2. qhfl-6 购物车

    购物车中心 用户点击价格策略加入购物车,个人中心可以查看自己所有购物车中数据 在购物车中可以删除课程,还可以更新购物车中课程的价格策略 所以接口应该有四种请求方式, get,post,patch,de ...

  3. brew安装和换源

    命令行执行ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&q ...

  4. python学习第五章

    1.继承 即是一个派生的类(derived class)继承基类(base class)的字段和方法,继承也允许把一个 派生类的对象作为 一个基类 对象对待.通俗来讲就是方便,继承前人的代码,减少工作 ...

  5. 冲刺博客NO.7

    今天做了什么: 在Iconfont-阿里巴巴矢量图标库找了个图标,仍感觉不是很好看. 查询函数遇到了很多困难 困难:不会真机测试,链接USB后 adb没检测到设备(包括真机和虚拟机). 在Termin ...

  6. 人脸识别准备 -- 基于raspberry pi 3b + movidius

    最近准备系统地学习一下深度学习和TensorFlow,就以人脸识别作为目的. 十年前我做过一些图像处理相关的项目和研究,涉及到图像检索.记得当时使用的是SIFT特征提取,该特征算子能很好地抵抗图像旋转 ...

  7. [转]data-driven与决策树聚类的两种方法

    参考文章: http://blog.csdn.net/quheDiegooo/article/details/60873999 http://blog.csdn.net/quhediegooo/art ...

  8. 2019-4-22 linux学习

    linux 一.linux的目录结构 /          挂载目录:为所有目录的根目录 home  家目录:    用户的根目录 存放普通用户的文件 例如:创建一个jack用户,就会产生一个Jack ...

  9. SpringCloud总结

    初级入门使用轮廓,整理一下思路

  10. Python 有序字典(OrderedDict)与 普通字典(dict)

    Python 的基础数据类型中的字典类型分为:无序字典 与 有序字典 两种类型 1.无序字典(普通字典): my_dict = dict()my_dict["name"] = &q ...