一、函数

1.1 函数与方法

Scala 中函数与方法的区别非常小,如果函数作为某个对象的成员,这样的函数被称为方法,否则就是一个正常的函数。

  1. // 定义方法
  2. def multi1(x:Int) = {x * x}
  3. // 定义函数
  4. val multi2 = (x: Int) => {x * x}
  5. println(multi1(3)) //输出 9
  6. println(multi2(3)) //输出 9

也可以使用 def 定义函数:

  1. def multi3 = (x: Int) => {x * x}
  2. println(multi3(3)) //输出 9

multi2multi3 本质上没有区别,这是因为函数是一等公民,val multi2 = (x: Int) => {x * x} 这个语句相当于是使用 def 预先定义了函数,之后赋值给变量 multi2

1.2 函数类型

上面我们说过 multi2multi3 本质上是一样的,那么作为函数它们是什么类型的?两者的类型实际上都是 Int => Int,前面一个 Int 代表输入参数类型,后面一个 Int 代表返回值类型。

  1. scala> val multi2 = (x: Int) => {x * x}
  2. multi2: Int => Int = $$Lambda$1092/594363215@1dd1a777
  3. scala> def multi3 = (x: Int) => {x * x}
  4. multi3: Int => Int
  5. // 如果有多个参数,则类型为:(参数类型,参数类型 ...)=>返回值类型
  6. scala> val multi4 = (x: Int,name: String) => {name + x * x }
  7. multi4: (Int, String) => String = $$Lambda$1093/1039732747@2eb4fe7

1.3 一等公民&匿名函数

在 Scala 中函数是一等公民,这意味着不仅可以定义函数并调用它们,还可以将它们作为值进行传递:

  1. import scala.math.ceil
  2. object ScalaApp extends App {
  3. // 将函数 ceil 赋值给变量 fun,使用下划线 (_) 指明是 ceil 函数但不传递参数
  4. val fun = ceil _
  5. println(fun(2.3456)) //输出 3.0
  6. }

在 Scala 中你不必给每一个函数都命名,如 (x: Int) => 3 * x 就是一个匿名函数:

  1. object ScalaApp extends App {
  2. // 1.匿名函数
  3. (x: Int) => 3 * x
  4. // 2.具名函数
  5. val fun = (x: Int) => 3 * x
  6. // 3.直接使用匿名函数
  7. val array01 = Array(1, 2, 3).map((x: Int) => 3 * x)
  8. // 4.使用占位符简写匿名函数
  9. val array02 = Array(1, 2, 3).map(_ * 3)
  10. // 5.使用具名函数
  11. val array03 = Array(1, 2, 3).map(fun)
  12. }

1.4 特殊的函数表达式

1. 可变长度参数列表

在 Java 中如果你想要传递可变长度的参数,需要使用 String ...args 这种形式,Scala 中等效的表达为 args: String*

  1. object ScalaApp extends App {
  2. def echo(args: String*): Unit = {
  3. for (arg <- args) println(arg)
  4. }
  5. echo("spark","hadoop","flink")
  6. }
  7. // 输出
  8. spark
  9. hadoop
  10. flink

2. 传递具名参数

向函数传递参数时候可以指定具体的参数名。

  1. object ScalaApp extends App {
  2. def detail(name: String, age: Int): Unit = println(name + ":" + age)
  3. // 1.按照参数定义的顺序传入
  4. detail("heibaiying", 12)
  5. // 2.传递参数的时候指定具体的名称,则不必遵循定义的顺序
  6. detail(age = 12, name = "heibaiying")
  7. }

3. 默认值参数

在定义函数时,可以为参数指定默认值。

  1. object ScalaApp extends App {
  2. def detail(name: String, age: Int = 88): Unit = println(name + ":" + age)
  3. // 如果没有传递 age 值,则使用默认值
  4. detail("heibaiying")
  5. detail("heibaiying", 12)
  6. }

二、闭包

2.1 闭包的定义

  1. var more = 10
  2. // addMore 一个闭包函数:因为其捕获了自由变量 more 从而闭合了该函数字面量
  3. val addMore = (x: Int) => x + more

如上函数 addMore 中有两个变量 x 和 more:

  • x : 是一个绑定变量 (bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;
  • more : 是一个自由变量 (free variable),因为函数字面量本生并没有给 more 赋予任何含义。

按照定义:在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。

2.2 修改自由变量

这里需要注意的是,闭包捕获的是变量本身,即是对变量本身的引用,这意味着:

  • 闭包外部对自由变量的修改,在闭包内部是可见的;
  • 闭包内部对自由变量的修改,在闭包外部也是可见的。
  1. // 声明 more 变量
  2. scala> var more = 10
  3. more: Int = 10
  4. // more 变量必须已经被声明,否则下面的语句会报错
  5. scala> val addMore = (x: Int) => {x + more}
  6. addMore: Int => Int = $$Lambda$1076/1844473121@876c4f0
  7. scala> addMore(10)
  8. res7: Int = 20
  9. // 注意这里是给 more 变量赋值,而不是重新声明 more 变量
  10. scala> more=1000
  11. more: Int = 1000
  12. scala> addMore(10)
  13. res8: Int = 1010

2.3 自由变量多副本

自由变量可能随着程序的改变而改变,从而产生多个副本,但是闭包永远指向创建时候有效的那个变量副本。

  1. // 第一次声明 more 变量
  2. scala> var more = 10
  3. more: Int = 10
  4. // 创建闭包函数
  5. scala> val addMore10 = (x: Int) => {x + more}
  6. addMore10: Int => Int = $$Lambda$1077/1144251618@1bdaa13c
  7. // 调用闭包函数
  8. scala> addMore10(9)
  9. res9: Int = 19
  10. // 重新声明 more 变量
  11. scala> var more = 100
  12. more: Int = 100
  13. // 创建新的闭包函数
  14. scala> val addMore100 = (x: Int) => {x + more}
  15. addMore100: Int => Int = $$Lambda$1078/626955849@4d0be2ac
  16. // 引用的是重新声明 more 变量
  17. scala> addMore100(9)
  18. res10: Int = 109
  19. // 引用的还是第一次声明的 more 变量
  20. scala> addMore10(9)
  21. res11: Int = 19
  22. // 对于全局而言 more 还是 100
  23. scala> more
  24. res12: Int = 100

从上面的示例可以看出重新声明 more 后,全局的 more 的值是 100,但是对于闭包函数 addMore10 还是引用的是值为 10 的 more,这是由虚拟机来实现的,虚拟机会保证 more 变量在重新声明后,原来的被捕获的变量副本继续在堆上保持存活。

三、高阶函数

3.1 使用函数作为参数

定义函数时候支持传入函数作为参数,此时新定义的函数被称为高阶函数。

  1. object ScalaApp extends App {
  2. // 1.定义函数
  3. def square = (x: Int) => {
  4. x * x
  5. }
  6. // 2.定义高阶函数: 第一个参数是类型为 Int => Int 的函数
  7. def multi(fun: Int => Int, x: Int) = {
  8. fun(x) * 100
  9. }
  10. // 3.传入具名函数
  11. println(multi(square, 5)) // 输出 2500
  12. // 4.传入匿名函数
  13. println(multi(_ * 100, 5)) // 输出 50000
  14. }

3.2 函数柯里化

我们上面定义的函数都只支持一个参数列表,而柯里化函数则支持多个参数列表。柯里化指的是将原来接受两个参数的函数变成接受一个参数的函数的过程。新的函数以原有第二个参数作为参数。

  1. object ScalaApp extends App {
  2. // 定义柯里化函数
  3. def curriedSum(x: Int)(y: Int) = x + y
  4. println(curriedSum(2)(3)) //输出 5
  5. }

这里当你调用 curriedSum 时候,实际上是连着做了两次传统的函数调用,实际执行的柯里化过程如下:

  • 第一次调用接收一个名为 x 的 Int 型参数,返回一个用于第二次调用的函数,假设 x 为 2,则返回函数 2+y
  • 返回的函数接收参数 y,并计算并返回值 2+3 的值。

想要获得柯里化的中间返回的函数其实也比较简单:

  1. object ScalaApp extends App {
  2. // 定义柯里化函数
  3. def curriedSum(x: Int)(y: Int) = x + y
  4. println(curriedSum(2)(3)) //输出 5
  5. // 获取传入值为 10 返回的中间函数 10 + y
  6. val plus: Int => Int = curriedSum(10)_
  7. println(plus(3)) //输出值 13
  8. }

柯里化支持多个参数列表,多个参数按照从左到右的顺序依次执行柯里化操作:

  1. object ScalaApp extends App {
  2. // 定义柯里化函数
  3. def curriedSum(x: Int)(y: Int)(z: String) = x + y + z
  4. println(curriedSum(2)(3)("name")) // 输出 5name
  5. }

参考资料

  1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
  2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

更多大数据系列文章可以参见 GitHub 开源项目大数据入门指南

Scala 系列(十)—— 函数 & 闭包 & 柯里化的更多相关文章

  1. Scala 学习之路(十)—— 函数 & 闭包 & 柯里化

    一.函数 1.1 函数与方法 Scala中函数与方法的区别非常小,如果函数作为某个对象的成员,这样的函数被称为方法,否则就是一个正常的函数. // 定义方法 def multi1(x:Int) = { ...

  2. JS 函数的柯里化与反柯里化

    ===================================== 函数的柯里化与反柯里化 ===================================== [这是一篇比较久之前的总 ...

  3. 浅析 JavaScript 中的 函数 currying 柯里化

    原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字 ...

  4. Scala_方法、函数、柯里化

    方法.函数.柯里化 方法 声明方法: scala> def m1(x:Int,y:Int):Int = {     | x + y     | }m1: (x: Int, y: Int)Ints ...

  5. js高阶函数--判断数据类型、函数胡柯里化;

    一.判断数据类型: 常见的判断有typeof.instanceof. constructor. prototype,先来看typeof: var a = "hello world" ...

  6. 关于arguments对象以及函数的柯里化;

    1.arguments对象 Arguments是个类似数组但不是数组的对象,说他类似数组是因为其具备数组相同的访问性质及方式,能够由arguments[n]来访问对应的单个参数的值,并拥有数组长度属性 ...

  7. JavaScript函数式编程(纯函数、柯里化以及组合函数)

    JavaScript函数式编程(纯函数.柯里化以及组合函数) 前言 函数式编程(Functional Programming),又称为泛函编程,是一种编程范式.早在很久以前就提出了函数式编程这个概念了 ...

  8. 高频重要前端API手写整理(call,apply,bind,instanceof,flat,filter,new,防抖,节流,深浅拷贝,数组乱序,数组去重,继承, lazyman,jsonp的实现,函数的柯里化 )

    Function.prototype.call = function(context,...args){ var context = context || window; context.fn = t ...

  9. 理解运用JS的闭包、高阶函数、柯里化

    JS的闭包,是一个谈论得比较多的话题了,不过细细想来,有些人还是理不清闭包的概念定义以及相关的特性. 这里就整理一些,做个总结. 一.闭包 1. 闭包的概念 闭包与执行上下文.环境.作用域息息相关 执 ...

随机推荐

  1. python红蓝英雄大乱斗(面向对象实现)

    红蓝英雄大乱斗 游戏规则 ''' 有红蓝两方英雄(可自定义个数) 随机一方英雄使用随机攻击方式攻击另一方英雄,任意一方英雄全部阵亡则游戏结束 每个英雄有 名字.生命值.普通攻击.Q技能攻击.W技能攻击 ...

  2. Codeforces比赛注意事项(英语比较好,能翻译题目的可以跳过此文章)

    由题目可知,这篇文章是讲翻译文章的. 当然不是教英语啦 其实cf的比赛对于本蒟蒻最大的挑战就是翻译题目啦 所以我比赛时只能靠各种翻译器去无耻地翻译,然后读中文. 目前较好的翻译器有:百度,谷歌,有道. ...

  3. 你真的熟练使用webpack吗?

    https://www.webpackjs.com/ 官网地址 当自己在简历中写着熟练使用webpack的时候,殊不知自己只是在vue脚手架,react脚手架的路上走着比较轻松而已. 当面试官问你这几 ...

  4. Deque 和Queue

    概述 接口,一个线性结合,支持在集合首尾add , remove , deque 是double  ended queue 的缩写,意味双端队列,接口提供的方法有两种类型,如果失败,一种抛出异常,一种 ...

  5. Java编程基础阶段笔记 day04 Java基础语法(下)

    day04 Java基础语法 (下) 笔记Notes要点 switch-case语句注意 switch-case题目(switchTest5) 循环执行顺序 if-else 实现3个整数排序 Stri ...

  6. python_0基础学习_day01

    Python是一门动态解释型的强类型定义语言 一.变量 变量命名规则 由数字.字母.下划线组成 不能以数字开头 要具有描述性 要区分大小写 禁止使用python的关键字(在pycharm中关键字明明变 ...

  7. Placement_pools on Rados-GW

    The purpose of this test is to map a RadosGw Bucket to a specific Ceph pool. For exemple, if using a ...

  8. iview中page组件的跳转功能BUG解决方案

    xl_echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!! 在 ...

  9. java根据经纬度查询门店地理位置-完美解决附近门店问题

    1.首先我们需要创建一个门店表如下: CREATE TABLE `app_store` ( `store_id` ) NOT NULL AUTO_INCREMENT COMMENT '发布id', ` ...

  10. 如何用Hexo+Github创建自己的技术博客

    注册一个github GitHub官网.按照一般的网站注册登录执行就好了,不详细说. 安装git 安装很简单,一直下一步 git安装教程 很多教程里都说要配置环境变量,我本人安装过5次左右的git,一 ...