Scala中方法和函数的区别

16 浏览
0 Comments

Scala中方法和函数的区别

我读了Scala函数 (Another tour of Scala 的一部分)。在那篇文章中, 他说:

方法和函数不是同一样东西

但是他并没有解释任何关于它的东西。他是想说什么呢?

admin 更改状态以发布 2023年5月23日
0
0 Comments

一个方法和函数之间的一个重要实际区别是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

0
0 Comments

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除其正文以外的所有内容。

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

函数声明是一个包含类型和主体的def声明。类型部分是方法类型,而主体可以是表达式或是块。在JVM上,Java使用“方法”来实现此功能。

匿名函数是函数类型的实例(即实例化FunctionN特质),而方法值与此相同!区别在于,方法值是从方法创建的,通过在方法之后加下划线(m_是对应“函数声明”(defm的方法值),或者通过名为eta-expansion的过程,这类似于从方法到函数的自动转换。

这就是规范中所说的,因此让我把它放在前面:我们不使用这个术语!这会在所谓的“函数声明”(它是程序的一部分(第4章-基本声明))和“匿名函数”(它是一个表达式)以及“函数类型”(一个特质)之间导致太多的混淆。

以下术语是由有经验的Scala编程人员使用并进行了更改的: 我们将函数声明称为方法。甚至可以称为方法声明。此外,我们还注意到,值声明和变量声明在实际目的上也是方法。

因此,根据上述术语的更改,这里是对区别的实际解释。

函数是一个对象,包括其中一个FunctionX特质,如Function0Function1Function2等。它可能还包括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 ""

我认为这几乎涵盖了所有内容,但我将很乐意用对任何余下问题的回答来补充这个内容。

0