Scala中方法和函数的区别
Scala中方法和函数的区别
我读了Scala函数 (Another tour of Scala 的一部分)。在那篇文章中, 他说:
方法和函数不是同一样东西
但是他并没有解释任何关于它的东西。他是想说什么呢?
一个方法和函数之间的一个重要实际区别是return
的含义。 return
只会从一个方法中返回。例如:
scala> val f = () => { return "test" }: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
Jim已经在他的博客文章中详细讲解了这个问题,但我在这里发布一个简要说明供参考。
首先,让我们看看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的一种类型,它是标准库中trait FunctionN
的缩写。匿名函数和方法值具有函数类型,并且函数类型可以用作值、变量和函数的声明和定义的一部分。实际上,它可以是方法类型的一部分。
方法类型是一种非值类型。这意味着没有值-没有对象、没有实例-具有方法类型。正如上面所提到的,方法值事实上具有函数类型。方法类型是一种def
声明-关于def
除其正文以外的所有内容。
值声明和定义和变量声明和定义是val
和var
声明,包括类型和值-可以分别是函数类型和匿名函数或方法值。请注意,在JVM上,这些(方法值)是使用Java称为“方法”的东西来实现的。
函数声明是一个包含类型和主体的def
声明。类型部分是方法类型,而主体可以是表达式或是块。在JVM上,Java使用“方法”来实现此功能。
匿名函数是函数类型的实例(即实例化FunctionN
特质),而方法值与此相同!区别在于,方法值是从方法创建的,通过在方法之后加下划线(m_
是对应“函数声明”(def
)m
的方法值),或者通过名为eta-expansion的过程,这类似于从方法到函数的自动转换。
这就是规范中所说的,因此让我把它放在前面:我们不使用这个术语!这会在所谓的“函数声明”(它是程序的一部分(第4章-基本声明))和“匿名函数”(它是一个表达式)以及“函数类型”(一个特质)之间导致太多的混淆。
以下术语是由有经验的Scala编程人员使用并进行了更改的: 我们将函数声明称为方法。甚至可以称为方法声明。此外,我们还注意到,值声明和变量声明在实际目的上也是方法。
因此,根据上述术语的更改,这里是对区别的实际解释。
函数是一个对象,包括其中一个FunctionX
特质,如Function0
、Function1
、Function2
等。它可能还包括PartialFunction
,它实际上是扩展了Function1
。
让我们来看看其中一个特性的类型签名:
trait Function2[-T1, -T2, +R] extends AnyRef
这个特性有一个抽象方法(它还有一些具体方法):
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
是局部于作用域的方法,则两者都可以这样调用:
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 ""
我认为这几乎涵盖了所有内容,但我将很乐意用对任何余下问题的回答来补充这个内容。