Jim 在 his blog post 中对此进行了详细介绍,但我在此处发布简报以供参考。
首先,让我们看看 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
的简写。 匿名函数和方法值具有函数类型,函数类型可以用作值、变量和函数声明和定义的一部分。事实上,它可以是方法类型的一部分。
方法类型是非值类型。这意味着有 no 值 - 没有对象,没有实例 - 具有方法类型。如上所述,一个方法值实际上有一个函数类型。方法类型是一个 def
声明 - 关于 def
的所有内容,除了它的主体。
Value Declarations and Definitions 和 Variable Declarations and Definitions 是 val
和 var
声明,包括 type 和 value - 可以是, 函数类型和匿名函数或方法值。请注意,在 JVM 上,这些(方法值)是使用 Java 所谓的“方法”实现的。
Function Declaration 是一个 def
声明,包括 type 和 body。类型部分是方法类型,主体是表达式或块。这也通过 Java 所谓的“方法”在 JVM 上实现。
最后,匿名函数是函数类型的实例(即特征FunctionN
的实例)和方法值是一样的!区别在于方法值是从方法创建的,或者通过后缀下划线(m _
是对应于“函数声明”(def
)m
的方法值),或者通过名为 的过程eta-expansion,就像从方法到函数的自动转换。
这就是规格所说的,所以让我把它放在前面:我们不使用那个术语!它导致了所谓的“函数声明”(作为程序的一部分(第 4 章——基本声明))和“匿名函数”(一个表达式)和“函数类型”之间的太多混淆,即,一种类型——一种特征。
下面的术语由经验丰富的 Scala 程序员使用,与规范中的术语相比有一个变化:我们说的不是函数声明,而是方法。甚至方法声明。此外,我们注意到值声明和变量声明也是实用的方法。
因此,鉴于上述术语的变化,这里是对区别的实际解释。
function 是一个包含 FunctionX
特征之一的对象,例如 Function0
、Function1
、Function2
等。它也可能包含 PartialFunction
,实际上扩展 Function1
。
让我们看看这些特征之一的类型签名:
trait Function2[-T1, -T2, +R] extends AnyRef
这个特征有一个抽象方法(它也有一些具体方法):
def apply(v1: T1, v2: T2): R
这告诉我们所有关于它的知识。 function 有一个 apply
方法,它接收 T1、T2、... 类型的 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 _
假设 m
类型为 (List[Int])AnyRef
,Scala 会将 that 扩展为(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 ""
我认为这几乎涵盖了所有内容,但我很乐意通过回答可能存在的任何问题来补充这一点。
方法和函数之间的一个很大的实际区别是 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
for (a <- List(1, 2, 3)) { return ... }
内部返回怎么样?这会导致关闭。
return
从函数返回一个值,并从方法返回某种形式的 escape
或 break
或 continue
。
函数 可以使用参数列表调用函数以产生结果。函数具有参数列表、主体和结果类型。作为类、特征或单例对象成员的函数称为方法。在其他函数中定义的函数称为局部函数。结果类型为 Unit 的函数称为过程。源代码中的匿名函数称为函数字面量。在运行时,函数字面量被实例化为称为函数值的对象。
Programming in Scala Second Edition. Martin Odersky - Lex Spoon - Bill Venners
假设你有一个列表
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
检查下面的 Tutorial,它解释了通过其他示例传递其他差异,例如使用 Method Vs Function、使用函数作为变量、创建返回函数的函数
函数不支持参数默认值。方法可以。从方法转换为函数会丢失参数默认值。 (斯卡拉 2.8.1)
有一篇很好的文章here,我的大部分描述都来自该文章。关于我的理解,只是对函数和方法的简短比较。希望能帮助到你:
功能:它们基本上是一个对象。更准确地说,函数是具有 apply 方法的对象。因此,由于它们的开销,它们比方法慢一点。它类似于静态方法,因为它们独立于要调用的对象。一个简单的函数示例如下所示:
val f1 = (x: Int) => x + x
f1(2) // 4
上面的行只是将一个对象分配给另一个对象,例如 object1 = object2。实际上,我们示例中的 object2 是一个匿名函数,因此左侧获取对象的类型。因此,现在 f1 是一个对象(函数)。匿名函数实际上是 Function1[Int, Int] 的一个实例,表示一个函数有 1 个 Int 类型的参数和 Int 类型的返回值。不带参数调用 f1 会给我们匿名函数的签名 (Int => Int = )
方法:它们不是对象,而是分配给类的实例,即对象。与 java 中的方法或 c++ 中的成员函数完全相同(如 Raffi Khatchadourian 在对 this question 的评论中指出的)等。方法的简单示例如下所示:
def m1(x: Int) = x + x
m1(2) // 4
上面这行不是简单的赋值,而是一个方法的定义。当您像第二行一样使用值 2 调用此方法时,x 将替换为 2 并且将计算结果并得到 4 作为输出。如果只是简单地写 m1 在这里你会得到一个错误,因为它是方法并且需要输入值。通过使用 _ 您可以将方法分配给如下函数:
val f2 = m1 _ // Int => Int = <function1>
这是 Rob Norris 的精彩post,它解释了差异,这是一个 TL;DR
Scala 中的方法不是值,而是函数。您可以构造一个通过 η-expansion 委托给方法的函数(由尾随的下划线 thingy 触发)。
具有以下定义:
方法是用 def 定义的东西,值是可以分配给 val 的东西
简而言之(摘自博客):
当我们定义一个方法时,我们发现我们不能将它分配给 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
的 type,它看起来不正常;您不能声明 (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 程序员只需要知道以下三个规则即可正确使用函数和方法:
def 定义的方法和 => 定义的函数字面量都是函数。它在《Scala 编程》第 4 版第 8 章第 143 页中定义。
函数值是可以作为任何值传递的对象。函数文字和部分应用的函数是函数值。
如果代码中的某个点需要函数值,则可以省略部分应用函数的下划线。例如: someNumber.foreach(println)
Programming in Scala 出版了四个版本之后,人们仍然很难区分函数和函数值这两个重要概念,因为所有版本都没有给出明确的解释。语言规范太复杂了。我发现上面的规则简单而准确。
在 Scala 2.13 中,与函数不同,方法可以接受/返回
类型参数(多态方法)
隐式参数
依赖类型
但是,Polymorphic function types #4672 在 dotty (Scala 3) 中取消了这些限制,例如,dotty 版本 0.23.0-RC1 启用了 following syntax
类型参数
def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
隐式参数(context 个参数)
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
有关更多示例,请参阅 tests/run/polymorphic-functions.scala
它们之间最重要的区别在于,方法是属于对象的定义(通常是定义它的类、特征或对象),而函数本身就是一个值,因为在 Scala 中,每个值都是一个对象,因此,函数就是对象。
例如,这是一个方法:
def timesTwoMethod(x :Int): Int = x * 2
这是一个功能:
def timesTwoFunction = (x: Int) => x * 2
两者之间的真正区别是什么?
真正的区别在于第二个是对象。它的类型是 Int => Int
,或者更具体地说是 Function1[Int, Int]
。
为什么 Scala 将函数视为对象?
他们这样做是为了让您可以将函数作为参数传递给其他函数,因为 Scala 中的函数是一等公民。
然而,在某些情况下,Scala 可以通过一种重要的机制将方法或多或少地视为函数。
例如,在类型 List
上定义的高阶函数 map
接收另一个函数 f
作为其定义中的唯一参数,类型为 f: A => B
。接下来的两行都编译、运行并输出相同的结果:
List(1, 2, 3).map(timesTwoMethod)
List(1, 2, 3).map(timesTwoFunction)
为什么这行得通?
这是因为存在在 Scala 编译器中实现的称为 Eta Expansion 的机制。它所做的是将方法类型的表达式转换为函数类型的等效表达式,并且它无缝而安静地完成了。
这就是为什么上面的两行是等价的。
方法对对象进行操作,但函数不会。
Scala 和 C++ 有 Fuction,但在 JAVA 中,你必须用静态方法来模仿它们。
val f = m
的扩展引用为val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
时,您应该指出apply
方法中的this
不是引用AnyRef
对象,而是在其方法中评估val f = m _
的对象(可以说是 outerthis
),因为this
是闭包捕获的值之一(例如return
如下所述)。