Scala中的方法与函数

简介: 封装是计算机编程语言中最基本但也十分经典的思想(更严谨地说封装是面向对象设计中的一种思想),例如将一段频繁用到的逻辑写成一个函数的过程,其背后蕴含的其实就是封装的思想。与众多编程语言类似,Scala中也提供了方法和函数的功能,但在具体使用上又有很多特别之处,甚至一定程度上可以彰显Scala的设计理念。

640.png

本文旨在介绍Scala中方法和函数的常用用法,对一些少用而略显怪异的功能不予关注。主要行文目录如下:

  • 方法的常用用法
  • 标准定义
  • 参数默认值、带名传参和不定长参数
  • 参数列表缺省
  • return缺省

  • 返回值类型缺省
  • 等号缺省
  • 大括号缺省

  • 函数的常用用法
  • 标准定义
  • 偏应用函数和偏函数
  • 柯里化函数
  • 高阶函数

  • 二者的联系与区别
  • 方法主要用于类和对象,函数主要用于传参和返回值
  • 函数是一个对象,可以赋值给一个变量
  • 二者可以部分转化


01 方法的常用用法


在多数编程语言中,方法其实属于广义上的函数:独立定义的叫做函数,定义在类中的函数一般称之为方法。然而在Scala中,二者的差异其实会更大,不仅有形式上的区别,更有用法上的不同。


方法的标准定义如下:


640.png


以上是一个标准的Scala方法定义程序,执行的是两个整数求和的操作,保留了方法定义中的每个要素,分别介绍如下:


  • def:方法定义关键字,即define的缩写,这与Python中函数定义关键字一致
  • fun:方法名,符合Scala中标识符的定义要求,一般采用小驼峰方式组织命名
  • fun后接一对小括号,用于接收一组参数定义
  • a:参数名,符合Scala中标识符的定义要求


  • 参数名的Int:声明参数类型。与Python中可选声明参数类型不同的是,Scala中的参数类型声明是必须项,而且程序编译时会执行类型检查(Python中的参数类型声明就是个形式,仅用于提示使用者而不做实际检查,挂羊头卖狗肉是可行的);但值得指出的参数类型可以使用声明类型的子类和支持隐式转换,例如某方法中参数声明类型为Any,那么实际上可以接受任何类型;某方法参数声明为Double,那么传入Int也是可以的


  • 方法参数小括号后的Int:返回值类型,多数情况下可以省略,此时由编译器执行类型推断得出;但当方法中存在递归调用时为必须项;
  • 方法后的=:用于赋值操作,相当于把方法体中的返回值赋予给调用该方法的变量,特殊情况下可省略,此时无论方法体中是否实际有返回结果,该方法的返回值均为空
  • 方法体中的大括号:在Scala中,大括号意味着将一组执行语句囊括为一个整体,并称之为代码块,代码块的最后一行代码的执行结果即是该方法的返回结果
  • 方法体中return:与Python中必须显示使用return关键字来表达返回值,Scala中的return是可选项,一般仅在需提前返回方法执行结果时才需使用(否则,就是以方法体代码块中的最后一句代码执行结果作为返回值)
  • 方法调用:使用方法名+相应参数即可,这与其他语言中类似


以上为Scala中方法的完整标准定义和调用,但在很多情况下可以省略其中的部分要素,例如:


1)当参数指定默认取值时,在调用时可缺省,这与Python中的带名参数调用方式是一致的。特别地,Scala中也支持类似Python的不定长参数,但具体形式与Python中略有区别,注意如下方法中参数nums声明类型Int后标注了*,代表nums是不定长的Int型参数:


scala> def fun1(nums:Int*):Int= nums.sum
def fun1(nums: Int*): Int
scala> fun1(1, 2)
val res7: Int = 3


2)方法调用时省略传参小括号。特殊情况下,当方法无需接收任何参数时,即参数为空,那么在调用该方法时则可省略小括号,直接写出方法名即可;更特殊地,如果一个方法无需接收任何参数,那么在定义方法时则可省略小括号的书写,此时在调用方法时则必须省略小括号:


scala> def sayHelloWorld : Unit = println("hello, world")
def sayHelloWorld: Unit
scala> sayHelloWorld
hello, world
scala> sayHelloWorld()
                    ^
       error: Unit does not take parameters


3)return缺省。绝大多数情况下可以省略return,此时方法体中的最后一句代码执行结果即为该方法的返回值,一般仅需在提前终止代码块执行并返回结果时才需使用,例如如下方法首先判断除数是否为0,若为0则提前返回:


scala> def fun2(a:Int, b:Int):Int={
     |   if(b==0) return -1
     |   a / b
     | }
def fun2(a: Int, b: Int): Int


4)返回值类型缺省。Scala中的一个典型特性就是支持类型推断,包括方法的返回值类型推断。既然可以自动推断,所以一般可以省略,但当方法具有递归调用时必须显示声明返回值类型:


scala> def fun3(num:Int):Int = if(num==1) 1 else fun3(num-1)*num
def fun3(num: Int): Int
scala> def fun4(num:Int) = if(num==1) 1 else fun4(num-1)*num
                                             ^
       error: recursive method fun4 needs result type


5)等号缺省。方法声明中的等号用于连接方法签名(即方法名和参数部分)和方法体(即大括号中的代码块),用以表示赋值。所以,当无需返回任何结果或者说返回值类型为空时(返回值类型为空用Unit表示),此时即可省略等号。省略等号意味着返回值类型一定为空,即使方法体中的代码块实际会产生非空的返回值。例如以下方法的返回结果与预期不一致:


scala> def fun4(a:Int, b:Int) {a+b}
                              ^
       warning: procedure syntax is deprecated: instead, add `: Unit =` to explicitly declare `fun4`'s return type
def fun4(a: Int, b: Int): Unit
scala> fun4(1, 2)  // 返回结果为空


6)大括号缺省。实际上这不是Scala特有的特性,即当方法体仅有单行代码时,无需显示写出大括号。这很容易理解:大括号的作用是将一组代码囊括为一个整体,而当代码块仅有单行代码时自然可以缺省。


注:等号和大括号不可同时缺省。


02 函数的常用用法


如果说Scala中的方法更像是其他语言中函数,那么Scala中的函数则更像是为实现函数式编程而特有的设计。在多数介绍Scala中函数的技术文章中,一般会提到这么一句:


函数是Scala中的一等公民。


实际上,称函数是一等公民,其实是相对于方法而言,即函数可以像任何其他对象那样赋值给一个变量,以参数或者返回值的身份作为方法的一部分,换句话说函数在Scala中具有和其他对象同等使用权限,而这是方法所不具备的。


与方法使用def作为关键字来声明不同,Scala中声明函数的关键字其实是“=>”,一个标准的函数声明如下:


640.png


在如上的函数声明中,仍然实现的是两个整数相加的功能,其中各要素介绍如下:

  • 函数参数即参数类型,用法与方法中类似
  • 建立参数与返回值映射,个人认为这是Scala中函数的一个标志性符号,作用类似于方法中的=,但不可缺省
  • 函数体与方法中的用法类似


实际上,在完成方法介绍之后,函数的用法其实会更简单,但需把握以下区别:

  1. 函数可以没有函数名,此时即为匿名函数;
  2. 函数无需指定返回值类型,不是可以缺省,而是不支持;


另一方面,由于函数可以像其他对象一样赋值给变量,所以如上函数的定义可以用一个变量接收,而后该变量即可像方法一样完成功能调用、像变量一样作为参数供其他方法调用或作为返回值。实际上,在Scala中,函数的主要作用其实就是作为方法的参数或返回值,此时即对应高阶函数,体现的即为Scala的函数式编程思想。


scala> val add = (a:Int, b:Int) => a + b
val add: (Int, Int) => Int = $Lambda$1225/569105801@27b3e5ed
scala> add(1, 2)
val res17: Int = 3


函数的用法有许多高级特性,这些在一定程度上丰富了Scala的语法特性,但也很容易对初学者造成很大困扰,下面仅就其中的几个简单展开介绍:


1)偏应用函数和偏函数。这是两个很容易搞混的概念,所以不妨首先给出英文原义:偏应用函数英文写法为partial applied function,偏函数为partial function。所以看到了英文写法,两个概念中的偏就很容易理解:与其翻译为"偏",实则表达的含义是"部分"。至于是否带应用的区别,则没那么直观:但表达的含义倒也算清晰:


  • 偏应用函数的"偏"侧重于参数个数层面,即可以先传入部分参数,剩余参数交由后续再传入
  • 偏函数的"偏"侧重于参数定义域层面,即仅对部分定义域范围内明确逻辑,而对其他则不予给出。比如在某些情况下有明显的业务逻辑,而在其他情况下则处于待定状态时,则可用偏函数实现


// 偏应用函数:先指定部分参数,再指定其余参数
scala> val add = (a:Int, b:Int) => a + b
val add: (Int, Int) => Int = $Lambda$1225/569105801@27b3e5ed
scala> val add1 = add(1, _:Int)
val add1: Int => Int = $Lambda$1229/509809074@6fab6ea2
scala> add1(2)
val res18: Int = 3
// 偏函数:仅对参数定义域的部分范围明确逻辑
scala> val fun:PartialFunction[Int, Double] = {
     | case x if(x>0) => x+1.0
     | }
val fun: PartialFunction[Int,Double] = <function1>


PartialFunction[Int, Double]中,第一个Int表示输入参数为Int,第二个Double表示返回值类型为Double。


2)柯里化函数。对于Scala中含有多个参数的方法,可以通过调整书写形式实现各参数的逐步指定。例如:


scala> def add(a:Int, b:Int)=a+b
def add(a: Int, b: Int): Int
scala> def addCurried(a:Int)(b:Int)=a+b
def addCurried(a: Int)(b: Int): Int
scala> val add1 = addCurried(1) _
val add1: Int => Int = $Lambda$1272/601949467@1d4c39c2
scala> add1(2)
val res22: Int = 3


如上代码中,通过将add方法的书写形式调整为addCurried中的书写形式,在后续调用中可以先明确部分参数,并将明确了部分参数的函数作为返回结果赋值给一个新的变量add1,注意这里add1实际上是一个函数。


可见,对一个方法柯里化的过程,其效果与偏应用函数实际上是有些类似的,明确了部分参数的方法的返回结果就叫做柯里化函数。这也是将方法的柯里化特性放在这里讲述的原因。


3)高阶函数。实际上,上述的偏应用函数、柯里化函数背后对应的都属于Scala中高阶函数的特性,即函数以一个返回值的身份出现在其他方法中。对于Scala中的一个方法定义,但参数或返回值是一个函数类型时,那么就称之为高阶函数(或者更严谨的说,是一个高阶方法),这也是Scala中函数式编程的直接体现。


实际上,将函数作为另一个函数的参数或者返回值,这一特性在Python中也是有所体现的。


03 二者的联系与区别


作为编程语言中常用的封装技巧,函数是必不可少的语法特性。在很多编程语言中,例如Python,方法和函数本无实质区别,但在Scala中却有很大差异。这些差异一方面是出于Scala语法特性的需要,另一方面也成就了函数式编程的精髓。概括而言,方法和函数的主要联系与区别包括:


  • 方法定义的关键字为def,函数定义的标志性符号则为=>
  • 函数必须接受参数列表(参数可以为空,但小括号不可省略);而方法中则可以省略参数列表甚至小括号,此时仅用于完成部分固定功能
  • 方法可以指定返回值类型,也可以缺省;而函数则不支持指定返回值类型

  • 函数与其他对象一致(所谓的一等公民),可以赋值给一个变量,也可作为一个方法的参数或返回值,此时即为高阶函数
  • 方法可以简单的通过"方法名+空格+_"转变化函数


640.png



目录
相关文章
|
7月前
|
分布式计算 Scala Spark
Scala【集合常用方法和函数操作(下)】
Scala【集合常用方法和函数操作(下)】
|
7月前
|
分布式计算 Scala Spark
Scala 【集合常用方法和函数操作-上】
Scala 【集合常用方法和函数操作-上】
|
5月前
|
前端开发 Scala
Scala并发编程的react、loop方法详解
在这个例子中,`MyActor`会无限循环接收和处理消息。当收到一个字符串消息时,它会打印出"Received: "加上消息内容。如果收到其他类型的消息,它会打印"Unknown message"。
29 1
|
6月前
|
前端开发 Scala
Scala并发编程的react、loop方法详解
在这个例子中,`MyActor`会无限循环接收和处理消息。当收到一个字符串消息时,它会打印出"Received: "加上消息内容。如果收到其他类型的消息,它会打印"Unknown message"。
32 0
|
6月前
|
Scala
【收藏】Scala常用方法(笔记)
【收藏】Scala常用方法(笔记)
38 0
|
7月前
|
编译器 Scala
认识scala中的函数
认识scala中的函数
61 5
|
7月前
|
Scala 容器
Scala学习--day04--集合、常用方法、案例实操 - WordCount TopN、不同省份的商品点击排行
Scala学习--day04--集合、常用方法、案例实操 - WordCount TopN、不同省份的商品点击排行
108 2
|
7月前
|
Scala
Scala函数和方法
Scala函数和方法
38 1
|
机器学习/深度学习 分布式计算 Java
Scala方法和函数
Scala方法和函数
101 0
|
Java 编译器 Shell
scala中的变量、方法、函数
Scala是一门多范式的编程语言,一种类似java的编程语言,是可扩展语言,并集成面向对象编程和函数式编程的各种特性的混合功能编程语言。 Scala被编译后在Java虚拟机上运行。
208 0
scala中的变量、方法、函数