Scala中方法和函数之间的区别

Difference between method and function in Scala

我阅读scala函数(scala的另一个教程的一部分)。他在那篇文章中说:

Methods and functions are not the same thing

但他什么也没解释。他想说什么?


吉姆在他的博客中对此进行了大量报道,但我在这里发布了一份简报以供参考。好的。

首先,让我们看看scala规范告诉我们什么。第3章(类型)介绍了函数类型(3.2.9)和方法类型(3.3.1)。第4章(基本声明)谈到了价值声明和定义(4.1)、变量声明和定义(4.2)以及函数声明和定义(4.6)。第6章(表达式)提到匿名函数(6.23)和方法值(6.7)。奇怪的是,函数值在3.2.9中只讲过一次,其他地方没有。好的。

函数类型(大致)是形式(T1,…,TN)=>U的类型,它是标准库中特性FunctionN的缩写。匿名函数和方法值具有函数类型,函数类型可以用作值、变量、函数声明和定义的一部分。实际上,它可以是方法类型的一部分。好的。

方法类型是非值类型。这意味着没有方法类型的值-没有对象,没有实例。如上所述,方法值实际上具有函数类型。方法类型是一个def声明——除了它的主体之外,所有关于def的内容。好的。

值声明和定义以及变量声明和定义是valvar声明,包括类型和值-分别是函数类型和匿名函数或方法值。注意,在JVM上,这些(方法值)是用Java调用的"方法"来实现的。好的。

函数声明是一个def声明,包括类型和主体。类型部分是方法类型,主体是表达式或块。这也在JVM上实现,Java调用了"方法"。好的。

最后,匿名函数是一个函数类型的实例(即特性FunctionN的实例),方法值也是一样的!区别在于,方法值是从方法创建的,要么是通过固定下划线(m _是对应于"函数声明"(defm的方法值),要么是通过名为eta扩展的过程创建的,这类似于从方法到函数的自动转换。好的。

规范就是这么说的,所以让我把这个放在前面:我们不使用这个术语!它导致了所谓的"函数声明"(属于程序的一部分)(第4章——基本声明)和"匿名函数"(表达式)以及"函数类型"(也就是类型——特性)之间的混淆。好的。

下面的术语是由经验丰富的scala程序员使用的,它与规范的术语有一个不同:我们说的不是函数声明,而是方法。甚至方法声明。此外,我们注意到值声明和变量声明也是用于实际目的的方法。好的。

因此,考虑到上述术语的变化,这里有一个实际的区别解释。好的。

函数是一个包含FunctionX特征之一的对象,如Function0Function1Function2等,它也可能包含PartialFunction,实际上扩展了Function1。好的。

让我们看看这些特性之一的类型签名:好的。

1
trait Function2[-T1, -T2, +R] extends AnyRef

这个特性有一个抽象的方法(它也有一些具体的方法):好的。

1
def apply(v1: T1, v2: T2): R

这就告诉了我们要知道的一切。函数有一个apply方法,它接收T1、T2、…、TN类型的n个参数,并返回R类型的一些参数。它在接收到的参数上是反变的,在结果上是同变的。好的。

这种差异意味着Function1[Seq[T], String]Function1[List[T], AnyRef]的一个亚型。作为子类型意味着可以用它来代替它。我们可以很容易地看到,如果我打电话给f(List(1, 2, 3)),希望AnyRef能回来,上面两种类型中的任何一种都可以工作。好的。

现在,一个方法和一个函数的相似性是什么?好吧,如果f是一个函数,而m是一个作用域局部的方法,那么两者都可以这样调用:好的。

1
2
val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))

这些调用实际上是不同的,因为第一个调用只是语法上的糖分。scala将其扩展到:好的。

1
val o1 = f.apply(List(1, 2, 3))

当然,它是对对象f的方法调用。函数还有其他的语法优势:函数文本(实际上是其中两个)和(T1, T2) => R类型的签名。例如:好的。

1
2
3
4
5
6
val f = (l: List[Int]) => l mkString""
val g: (AnyVal) => String = {
  case i: Int =>"Int"
  case d: Double =>"Double"
  case o =>"Other"
}

方法和函数之间的另一个相似之处是前者可以很容易地转换为后者:好的。

1
val f = m _

scala会将其扩展,假设m类型为(List[Int])AnyRef类型为(scala 2.7):好的。

1
2
3
val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

在scala 2.8中,它实际上使用一个AbstractFunction1类来减少类大小。好的。

注意,我们不能从另一个角度来转换——从一个函数转换为一个方法。好的。

然而,方法有一个很大的优势(好吧,两个——它们可以稍微快一点):它们可以接收类型参数。例如,虽然上面的f可以指定它接收的List的类型(示例中的List[Int]类型),但m可以参数化它:好的。

1
def m[T](l: List[T]): String = l mkString""

我认为这几乎涵盖了所有的事情,但我很乐意用对任何可能存在的问题的答案来补充这一点。好的。好啊。


一个方法和一个函数之间的一个巨大的实际区别是return的意思。return只从方法返回。例如:

1
2
3
4
scala> val f = () => { return"test" }
<console>:4: error: return outside method definition
       val f = () => { return"test" }
                       ^

从方法中定义的函数返回执行非本地返回:

1
2
3
4
5
6
7
8
9
scala> def f: String = {                
     |    val g = () => { return"test" }
     | g()                              
     |"not this"
     | }
f: String

scala> f
res4: String = test

而从本地方法返回只从该方法返回。

1
2
3
4
5
6
7
8
9
scala> def f2: String = {        
     | def g(): String = { return"test" }
     | g()
     |"is this"
     | }
f2: String

scala> f2
res5: String = is this


function A function can be invoked with a list of arguments to produce a
result. A function has a parameter list, a body, and a result type.
Functions that are members of a class, trait, or singleton object are
called methods. Functions defined inside other functions are called
local functions. Functions with the result type of Unit are called procedures.
Anonymous functions in source code are called function literals.
At run time, function literals are instantiated into objects called
function values.

在scala第二版中编程。马丁·奥德斯基-雷克斯·斯彭-比尔·文纳


假设你有一个清单

1
2
scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

定义一种方法

1
2
scala> def m1(i:Int)=i+2
m1: (i: Int)Int

定义函数

1
2
3
4
5
scala> (i:Int)=>i+2
res0: Int => Int = <function1>

scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)

方法接受参数

1
2
scala> m1(2)
res3: Int = 4

用val定义函数

1
2
scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>

函数的参数是可选的

1
2
3
4
5
 scala> p(2)
    res4: Int = 4

scala> p
res5: Int => Int = <function1>

方法的参数是必需的

1
2
3
scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function

检查下面的教程,该教程解释了如何通过其他示例传递其他差异,例如使用方法vs函数的diff示例,使用函数作为变量,创建返回函数的函数


函数不支持参数默认值。方法可以。从方法转换为函数将丢失参数默认值。(Scala 2.8-1)


这里有一篇很好的文章,我的大部分描述都是从中提取出来的。就我的理解而言,只是简单地比较一下函数和方法。希望它有帮助:

功能:它们基本上是一个物体。更准确地说,函数是具有apply方法的对象;因此,由于它们的开销,它们比方法慢一些。它类似于静态方法,因为它们独立于要调用的对象。函数的一个简单示例如下所示:

1
2
val f1 = (x: Int) => x + x
f1(2)  // 4

上面的行只是将一个对象分配给另一个对象,比如object1=object2。实际上,我们的示例中的object2是一个匿名函数,因此左侧得到了一个对象的类型。因此,现在f1是一个对象(函数)。匿名函数实际上是function1[int,int]的一个实例,它表示一个参数为int类型,返回值为int类型的函数。在没有参数的情况下调用f1将为我们提供匿名函数的签名(int=>int=)

方法:它们不是对象,而是分配给类的实例,即对象。与Java中的方法完全相同,或者C++中的成员函数(如Raffi Khatchadourian在这个问题的注释中指出的)等。一个简单的方法示例如下:

1
2
def m1(x: Int) = x + x
m1(2)  // 4

上面的行不是简单的值赋值,而是方法的定义。当使用值2(如第二行)调用此方法时,x替换为2,计算结果,得到4作为输出。这里,如果只写m1,就会得到一个错误,因为它是方法,需要输入值。通过使用,您可以将一个方法分配给如下函数:

1
val f2 = m1 _  // Int => Int = <function1>


这是罗布·诺里斯的一篇很好的文章,解释了两者的区别,这里是一篇TL;博士

Methods in Scala are not values, but functions are. You can construct a function that delegates to a method via η-expansion (triggered by the trailing underscore thingy).

定义如下:

a method is something defined with def and a value is something you can assign to a val

简而言之(摘自博客):

当我们定义一个方法时,我们看到我们不能将它赋给val

1
2
3
4
5
6
7
scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int

scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
       val f = add1

还要注意add1的类型,它看起来不正常;不能声明(n: Int)Int类型的变量。方法不是值。

但是,通过添加η-扩展后缀运算符(η发音为"eta"),我们可以将该方法转换为函数值。注意f的类型。

1
2
3
4
5
scala> val f = add1 _
f: Int => Int = <function1>

scala> f(3)
res0: Int = 4

_的效果相当于执行以下操作:我们构造了一个Function1实例来委托我们的方法。

1
2
3
4
5
scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>

scala> g(3)
res18: Int = 4