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

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

从方法中定义的函数返回一个非局部返回:

scala> def f: String = {
|    val g = () => { return "test" }
| g()
| "not this"
| }
f: String


scala> f
res4: String = test

而从局部方法返回只从该方法返回。

scala> def f2: String = {
| def g(): String = { return "test" }
| g()
| "is this"
| }
f2: String


scala> f2
res5: String = is this

吉姆已经在他的博客文章中得到了这一点,但我在这里发布了一个简报供参考。

首先,让我们看看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的缩写。匿名函数值方法有函数类型,函数类型可以用作值、变量和函数声明和定义的一部分。事实上,它可以是方法类型的一部分。

方法类型< em >无值型< / em >。这意味着有没有< em > < / em >值-没有对象,没有实例-具有方法类型。如上所述,法的价值实际上有函数类型。方法类型是一个def声明-关于def的一切,除了它的主体。

值声明和定义变量声明和定义valvar声明,包括类型和值——它们分别可以是函数类型匿名函数或方法值。注意,在JVM上,这些(方法值)是用Java所称的“方法”实现的。

函数声明是一个def声明,包括类型身体。类型部分是方法类型,主体是表达式或块。这也是在JVM上实现的,Java称之为“方法”。

最后,匿名函数函数类型的一个实例(即,trait FunctionN的一个实例),而法的价值也是一样!区别在于方法值是从方法中创建的,或者通过添加下划线(m _是一个对应于“函数声明”(def) m的方法值),或者通过名为eta-expansion的进程创建,这类似于从方法到函数的自动强制转换。

这在所谓的“函数声明”“匿名函数”之间造成了太多的混淆,“函数声明”是程序的一部分(第4章——基本声明),“匿名函数”是表达式,“函数式”是类型——一个trait。

下面的术语(由有经验的Scala程序员使用)对规范中的术语做了一个更改:我们不是说函数声明,而是说方法。甚至是方法声明。此外,我们注意到价值声明变量声明也是用于实际目的的方法。

因此,鉴于上述术语的变化,这里有一个对区别的实际解释。

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

让我们看看其中一个特征的类型签名:

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

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

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

这告诉了我们关于它的一切。函数有一个apply方法,它接收类型为T1T2,…的N参数。, TN,并返回类型为R的东西。它接收的参数是逆变的,结果是协变的。

这个方差意味着Function1[Seq[T], String]Function1[List[T], AnyRef]的子类型。作为子类型意味着它可以被使用代替它。很容易看出,如果我要调用f(List(1, 2, 3))并期望返回AnyRef,上面两种类型中的任何一种都可以工作。

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

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

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

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

当然,这是对对象f的方法调用。函数也有其他语法糖的优势:函数字面量(实际上是两个)和(T1, T2) => R类型签名。例如:

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

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

val f = m _

Scala将扩展,假设m类型为(List[Int])AnyRef为(Scala 2.7):

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可以参数化它:

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

我认为这几乎涵盖了所有内容,但我很乐意补充回答任何可能存在的问题。

函数不支持默认参数。做的方法。从方法转换到函数会丢失参数默认值。(Scala 2.8.1发布)

假设你有一个列表

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

定义一个方法

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

定义一个函数

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)

接受参数的方法

scala> m1(2)
res3: Int = 4

用val定义函数

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

函数的参数是可选的

 scala> p(2)
res4: Int = 4


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

方法的参数是强制的

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

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

函数可以使用参数列表调用函数来生成一个 结果。函数有一个形参列表、一个函数体和一个结果类型。 类、trait或单例对象的成员函数是 叫方法。在其他函数中定义的函数被调用 本地函数。结果类型为Unit的函数称为过程。 源代码中的匿名函数称为函数字面量。 在运行时,函数字面量被实例化为调用的对象 函数值。< / p >

编程在Scala第二版。 Martin Odersky - Lex Spoon - Bill Venners

有一篇很好的文章在这里,我的大部分描述都来自于它。 关于我的理解,只是一个简短的函数和方法的比较。

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

val f1 = (x: Int) => x + x
f1(2)  // 4
上面这行只是将一个对象分配给另一个对象,如object1 = object2。实际上,在我们的例子中,object2是一个匿名函数,因此左边得到了对象的类型。因此,现在f1是一个对象(函数)。匿名函数实际上是Function1[Int, Int]的一个实例,它意味着一个函数具有一个Int类型的参数和Int类型的返回值。 调用不带参数的f1将为我们提供匿名函数的签名(Int =>Int =)

< p > 方法: 它们不是对象,而是赋值给类的实例。,一个物体。与java中的方法或c++中的成员函数完全相同(正如的Raffi Khatchadourian这个问题的注释中指出的那样)等等。 一个简单的方法示例如下:

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

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

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

这是一个伟大的帖子由Rob Norris解释的区别,这是一个TL;DR

Scala中的方法不是值,但函数是。您可以构造一个通过η扩展(由后面的下划线触发)委托给某个方法的函数。

定义如下:

方法是用def定义的东西,而价值是可以赋值给瓦尔的东西

概括地说(摘自博客):

定义方法时,我们看到不能将其赋值给val

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的类型。

scala> val f = add1 _
f: Int => Int = <function1>


scala> f(3)
res0: Int = 4

_的作用相当于执行以下操作:构造一个委托给方法的Function1实例。

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


scala> g(3)
res18: Int = 4

在Scala 2.13中,与函数不同,方法可以接受/返回

  • 类型参数(多态方法)
  • 隐式参数
  • 从属类型

然而,这些限制在dotty (Scala 3)中通过多态函数类型#4672解除了,例如,dotty版本0.23.0-RC1启用了以下语法

类型参数

def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))

隐式参数(context parameters)

def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero

从属类型

class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet

更多示例请参见测试/运行/ polymorphic-functions.scala

实际上,Scala程序员只需要知道以下三条规则就可以正确使用函数和方法:

  • def定义的方法和由=>定义的函数字面量是函数。在《Programming in Scala》第4版第8章第143页中定义。
  • 函数值是可以作为任何值传递的对象。函数字面量和部分应用的函数是函数值。
  • 如果在代码中的某个位置需要函数值,则可以省略部分应用的函数的下划线。例如:someNumber.foreach(println)

在《Scala编程》发行了四个版本之后,区分函数和函数值这两个重要概念仍然是个问题,因为所有版本都没有给出明确的解释。语言规范太复杂了。我发现上面的规则既简单又准确。

方法操作对象,而函数不操作。

Scala和c++都有函数,但在JAVA中,你必须用静态方法来模仿它们。

方法属于一个对象(通常是你定义它的classtraitobject),而函数本身是一个值,并且由于在Scala中每个值都是一个对象,因此,函数是一个对象

例如,给定下面的方法和函数:

def timesTwoMethod(x :Int): Int = x * 2
def timesTwoFunction = (x: Int) => x * 2

第二个def是一个类型为Int => Int的对象(Function1[Int, Int]的语法糖)。

Scala将函数作为对象,这样它们就可以作为一级实体使用。通过这种方式,可以将函数作为参数传递给其他函数。

然而,Scala也可以通过一种名为埃塔扩张的机制将方法视为函数。

例如,定义在List上的高阶函数map接收另一个函数f: A => B作为其唯一参数。接下来的两行是等价的:

List(1, 2, 3).map(timesTwoMethod)
List(1, 2, 3).map(timesTwoFunction)

当编译器在需要函数的地方看到def时,它会自动将该方法转换为等效函数。

区别是细微的,但却是实质性的,它与所使用的类型系统有关(除了来自面向对象或函数范式的术语)。

当我们谈论< >强函数< / >强时,我们谈论的是类型函数:它是一种类型,它的实例可以作为输入或输出传递给其他函数(至少在Scala中是这样)。

当我们谈论(类的)< >强方法< / >强时,我们实际上是在谈论由它所属的类表示的类型:也就是说,方法只是一个更大类型的组件,并且不能单独传递。它必须与它所属类型的实例(即类的实例)一起传递。