Scala函数和方法比较

关于function和method,Scala规范中给出抽象的定义,理解它有助于深度领悟scala的编程思想,也能体会到function和method的本质区别。
首先要理清几个名词的关系:函数声明(Function declaration),函数值(Function value),方法类型(Method type)和方法值(Method value)。

函数声明 = 方法类型 + 函数体(表达式或者代码块)。重要的是,函数声明也就是方法(method),方法类型是它的组成部分,它给出了方法的描述(包括方法名和签名),但它不会直接执行。
Method = Function declaration = method type + body
Method type = just define method without value

函数类型定义了大量的函数抽象,用于生成函数值,而函数值就是函数(function)。函数类型本质是trait,它包含一个抽象apply方法,如下。

函数包括了匿名函数和方法值,而方法类型可以转换为方法值。
Function type = trait FunctionN with apply method
Function value = function = object instantiated by a class extends from FunctionN.

上面的描述是理论基础,在scala实践中函数(function)和方法(method)也是不同的。
函数本质是对象,是实现了 FunctionN trait的类的实例。Scala提供了各种不同参数数量的trait,如Function0,Function1…。而方法属于定义范畴,只有在调用的时候才会执行。
函数使用val定义,而方法使用def定义。所以函数在定义的时候就会执行,并开辟内存空间,大量的函数必定会产生内存问题,而方法则是在每次调用的时候才会执行。可以见函数更像一个变量,而方法则属于定义,更多用在类中实现功能。
方法可以转换为函数,而函数却不能转换为方法。方法转函数的方法是[method type] _,如下代码。

如上所说,函数是实例化的对象,所以它是有方法的,而方法则没有。

对于方法转函数,它的本质就是生成实例化的trait对象,所以每次执行会开辟不一样的内存空间,生成不同的对象。

Scala为函数的定义和调用提供了语法糖(Syntactic sugar),简化了函数的语法,当然也可以用完整的函数来定义和调用。如下代码,可以用lamda表达式来定义函数,也可以自己实现Function trait定义函数;可以用括号()来调用函数,也可以用apply方法调用函数,他们的效果是一样的。

方法支持类型参数(type-parameterized),即不指明参数的数据类型(可变类型),而函数语法糖不支持,因为函数要立刻执行,必须明确参数类型,而方法在调用的时候才会执行,此时指定参数是可以接受的。

如果函数想支持可变类型参数,必须自己实现trait,而不能使用lamda表达式。

函数定义可以有两种方式,一种是上面演示的匿名函数(anonymous function, 可以叫 => lamda表达式,也可以叫function literals),另一种是使用类型签名(type signature),如下代码。

参考:
http://jim-mcbeath.blogspot.com.au/2009/05/scala-functions-vs-methods.html
https://www.scala-lang.org/files/archive/spec/2.11/04-basic-declarations-and-definitions.html#default-arguments
https://www.scala-lang.org/files/archive/spec/2.11/03-types.html#function-types

Scala函数和方法比较

Scala函数的参数类型

方法的参数的调用有两种格式:按值(by-name)传递按名(by-name)传递。值参数是函数默认的参数格式,函数调用的时候所有值参数会立刻执行,而名参数在函数定义中需要用=>指定,它在函数调用的时候不会被立刻执行,只有在函数体内应用的时候才会执行。可以将按值传递参数想象成val,按名传递参数想想为def。

下面通过一个例子对比看两种参数传递方式。对于两种参数传递方式,如果参数是普通的数据类型(例如这里的字符串)返回结果是一样的,但如果参数是方法或者对象就会不一样了。对使用按值传递参数的函数mood,注意红框区域,两个println会在调用mood的时候执行,产生两个输出,而类似地将mood改为按名传递参数的函数,println不会在函数调用的时候立刻执行,会在函数体内需要的地方执行(例如,现在是2018-01-16,周二,那么只有println(“sad”)执行)。

正确使用两种参数类型不仅能解决函数调用中按需执行的问题,也能提高程序性能,减少内存消耗。

Scala函数的参数类型

Scala def和val比较

Scala 提供了四种变量定义的方式:defvallazy valvar。def定义的变量像是“占位符”,变量定义并不会执行,在每次调用变量的时候会执行;而val变量在定义的时候就会执行,且只执行一次;lazy val和val一样只会执行一次,但它是在使用的时候执行,不是在定义的时候。前面的def,(lazy) val用于定义不可变量,而var用于定义可变量,除此之外它和val一样的,所以下面demo不对var做演示。

一般情况下,def用于定义方法,val用于定义不可变量,var用于定义变量

先看一个简单的例子,它用于生成时间戳,他们分别使用val,def和lazy val定义。

从运行结果可以看出,val定义的timestamp1在定义的时候就被执行,且使用timestamp1定义的List中每个元素值是一样的。def定义的timestamp2在定义的时候并没有执行(因为没有执行println),然而在使用timestamp2定义的List中每个元素值是连续的三个值(因为线程sleep了1毫秒),且打印出现了三次,说明定义被执行了三次。对比前两个,lazy val定义的变量timestamp3并没有直接执行,而是在定义List的时候才执行,并且只执行了一次,这说明lazy val还是val,只是“聪明的“等到使用的时候才执行。

下面再看一个例子,isDivisibleBy用于生成一个函数(Int=>Boolean),它表示某数字是否可以被整除。

从代码执行结果可以看出,isEven1在定义的时候isDivisibleBy方法就执行了,所以isEven1的调用就等价于i%k==0,而isEven2在每次执行的时候才会调用isDivisibleBy方法(从println执行可以看出),所以isEven2的调用就等于isDivisibleBy(k)(i)。

def和val的概念在scala中很重要,它是理解方法和函数,值参数和名参数的基础,后面会有介绍。

Scala def和val比较