在Scala中,函数和方法的区别
Scala中的函数和方法之间的区别是什么?
在Scala中,函数和方法之间存在一些微妙的区别。首先,让我们看一下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声明,它包括了除了方法体之外的所有内容。
值声明和定义以及变量声明和定义是val和var声明,包括类型和值,可以分别是函数类型和匿名函数或方法值。需要注意的是,在JVM上,这些(方法值)是用Java称为“方法”的方式实现的。
函数声明是一个def声明,包括类型和函数体。类型部分是方法类型,函数体是一个表达式或一个代码块。这也是用Java称为“方法”的方式实现的。
最后,匿名函数是函数类型的实例(即,FunctionN特质的实例),方法值也是同样的东西!区别在于方法值是从方法中创建的,可以通过在方法后面加下划线(m _是一个对应于“函数声明”(def)m的方法值),或者通过一种称为eta扩展的过程进行,它类似于从方法到函数的自动转换。
这就是规范所说的,所以让我来解释一下:我们不使用那个术语!它会导致所谓的“函数声明”(程序的一部分,第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 ""
我认为这基本上涵盖了所有内容,但如果还有任何问题,我很乐意补充回答。
在Scala中,方法和函数之间的一个重要区别是关键字return
的含义。在方法中,return
只会返回方法本身。例如:
scala> val f = () => { return "test" } <console>:4: error: return outside method definition val f = () => { return "test" } ^
而在方法中定义的函数中,return
会进行非局部返回:
scala> def f: String = {
| val g = () => { return "test" }
| g()
| "not this"
| }
f: String
scala> f
res4: String = test
而在局部方法中的return
仅会返回该方法本身。
scala> def f2: String = {
| def g(): String = { return "test" }
| g()
| "is this"
| }
f2: String
scala> f2
res5: String = is this
这是因为闭包捕获了return
的值。
我无法想象有任何时候我会想要从函数中进行非局部范围的return
。实际上,如果一个函数可以决定它想要跳过更高层的代码,这可能导致严重的安全问题。感觉有点像longjmp,只是更容易出错。不过,我注意到scalac不允许我从函数中进行return
。这是否意味着这种滥用已经从语言中删除了呢?
- 那么从for (a <- List(1, 2, 3)) { return ... }
中返回呢?这将被转换为一个闭包。
嗯...好吧,这是一个合理的用例。但是仍然有可能导致可怕的难以调试的问题,但是这将其放在了更合理的上下文中。
当你需要“短路”一个折叠操作时,非局部返回也是更好的方法之一,例如:stackoverflow.com/a/12897950/154770
老实说,我会使用不同的语法。使用return
从函数中返回一个值,使用某种形式的escape
或break
或continue
从方法中返回。
在Scala中,函数和方法是编程中常见的两个概念。函数是一段可以通过传入一组参数产生结果的代码。函数拥有参数列表、函数体和返回值类型。而作为类、特质或单例对象的成员的函数则被称为方法。另外,定义在其他函数内部的函数被称为局部函数,而返回类型为Unit的函数被称为过程。在源代码中,匿名函数被称为函数字面量。在运行时,函数字面量会被实例化为称为函数值的对象。
在Scala中,函数和方法虽然有相似之处,但是它们在定义和使用上还是有一些区别的。具体而言,函数可以作为类的成员,通过def关键字来定义。而方法则是类的成员,通过val或var关键字来定义。值得注意的是,只有def定义的函数才能算作方法,val或var定义的函数则只能算作函数。
这种函数和方法的区别在Scala中的定义和使用上都是有意义的。通过将函数定义为方法,可以使其成为类的一部分,从而方便对该类的实例进行调用。而函数则可以作为独立的代码块存在,可以被传递给其他函数或保存为变量。这种灵活性使得函数可以更好地应对不同的编程需求。
总结起来,Scala中的函数和方法虽然有一些区别,但它们都是编程中常用的代码块,可以根据具体的需求选择使用。方法更适合作为类的一部分,而函数则更适合作为独立的代码块存在。通过合理地使用函数和方法,可以使代码更加灵活和可复用。
参考资料:
Programming in Scala Second Edition. Martin Odersky - Lex Spoon - Bill Venners