Scala中方法和函数之间的区别


254

我阅读了Scala函数Scala另一篇教程的一部分)。他在那篇文章中说:

方法和功能不一样

但是他没有任何解释。他想说什么?




有一个很好答案的后续问题:Scala中的函数与方法
Josiah Yoder,2016年

Answers:


238

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)=>Ü,这对于该性状的简写FunctionN在标准库。匿名函数方法值具有函数类型,并且函数类型可用作值,变量和函数的声明和定义的一部分。实际上,它可以是方法类型的一部分。

方法类型是一个非值类型。这意味着没有方法类型的值-没有对象,没有实例。如上所述,方法值实际上具有功能类型。方法类型是一个def声明-有关def其主体的所有内容。

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

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

最后,匿名函数函数类型的实例(即trait的实例FunctionN),而方法值是同一回事!区别在于,方法值是通过方法创建的,方法是后缀下划线(m _是对应于“函数声明”(def)的方法值m),也可以是称为eta-expansion的过程,这类似于方法的自动转换发挥作用。

规范就是这么说的,所以让我先说一下:我们不使用该术语!这导致程序的一部分(第4章-基本声明)即所谓的“函数声明”与作为表达式的“匿名函数”“函数类型”(即,一个类型-一个特质。

下面的术语,由经验丰富的Scala程序员使用,与规范的术语相比有一个变化:我们说方法而不是说函数声明。甚至方法声明。此外,我们注意到值声明变量声明也是实用的方法。

因此,鉴于上述术语的变化,以下是对该区别的实用解释。

函数是一个对象,包括所述的一个FunctionX特征,诸如Function0Function1Function2等。它可以被包括PartialFunction为好,这实际上延伸Function1

让我们看一下这些特征之一的类型签名:

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

此特征具有一种抽象方法(也具有一些具体方法):

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

这就告诉我们所有人都应该知道这一点。甲函数有一个apply接收方法Ñ的类型参数T1T2,...,TN,并且返回一些类型的R。它在接收到的参数上是反变量,在结果上是协变量。

这种差异意味着a 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))

当然,这是对object的方法调用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成(阶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 ""

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


26
这个解释很清楚。做得好。不幸的是,Odersky / Venners / Spoon书和Scala规范都在某种程度上互换使用了“功能”和“方法”一词。(他们最可能会说“函数”,其中“方法”会更清楚,但有时它也会以其他方式发生,例如,规范的第6.7节(将方法转换为函数)称为“方法值”。 。)我认为,当人们尝试学习该语言时,这些单词的松散使用引起了很多混乱。
塞斯·提苏

4
@Seth我知道,我知道-PinS是教我Scala的书。我通过艰苦的方式学得更好,例如,使我挺直。
Daniel C. Sobral

4
很好的解释!我要添加一件事:当引用val f = m编译器的扩展时,val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }您应该指出thisapply方法内部未引用AnyRef对象,而是方法val f = m _进行评估的对象(外部 this,即, ),因为this它是闭包捕获的值之一(例如,return如下面所指出的)。
Holger Peine 2013年

1
@ DanielC.Sobral,您提到的PinS书是什么?我也对学习Scala感兴趣,还没有找到一本同名的书
tldr

5
Otlsky等人的@tldr 在Scala中进行编程。这是它的常见缩写(由于某些原因,他们确实告诉我他们不完全喜欢PiS!:)
Daniel C. Sobral

67

方法和功能之间的一个巨大的实际差异是什么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

9
这是因为return被闭包捕获。
Daniel C. Sobral 2010年

4
我想不起来一次要从函数“返回”到非本地作用域的情况。实际上,我可以看到,如果一个函数可以确定它想进一步扩展到堆栈,这将是一个严重的安全问题。感觉有点像longjmp,只有这样容易出错。我注意到scalac不会让我从函数中返回。这是否意味着这种可恶已经从语言中删除了?

2
@root-从a内部返回for (a <- List(1, 2, 3)) { return ... }怎么办?这已成为现实。
Ben Lings

嗯...那是一个合理的用例。仍然有可能导致可怕的难以调试的问题,但这确实使它处于更明智的环境中。

1
老实说,我会使用不同的语法。有return从该函数返回一个值,和某种形式的escapebreakcontinue从方法返回。
瑞恩(Ryan)浸出

38

函数可以使用参数列表调用函数以产生结果。函数具有参数列表,主体和结果类型。属于类,特征或单例对象的成员的函数称为方法。在其他函数中定义的函数称为局部函数。结果类型为Unit的函数称为过程。源代码中的匿名函数称为函数文字。在运行时,函数文字被实例化为称为函数值的对象。

Scala第二版中的编程。马丁·奥德斯基-Lex Spoon-Bill Venners


1
函数可以属于def或val / var的类。只有def是方法。
2015年

29

假设您有清单

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

检查下面的教程,该教程说明如何通过示例传递其他差异,例如使用方法Vs函数比较diff的其他示例,将函数用作变量,创建返回函数的函数


13

函数不支持参数默认值。方法呢。从方法转换为函数将丢失参数默认值。(Scala 2.8.1)


5
有这个原因吗?
corazza 2014年

7

这里有一篇不错的文章从中可以得到我大多数的描述。关于我的理解,仅是功能和方法的简短比较。希望能帮助到你:

功能:它们基本上是一个对象。更确切地说,函数是具有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在对此问题的评论中指出)等完全相同。方法的一个简单示例就像下面这样:

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

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

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

“将方法分配给功能”是什么意思?这是否仅意味着您现在拥有的对象的行为与方法相同?
K. M

@KM:val f2 = m1 _等效于val f2 = new Function1 [Int,Int] {def m1(x:Int)= x + x};
佐助|

3

这是Rob Norris 的精彩文章,解释了区别,这是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,看起来并不正常; 您不能声明类型的变量(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

1

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

  • 类型参数(多态方法)
  • 隐式参数
  • 依赖类型

但是,多态函数类型#4672在dotty(Scala 3)中解除了这些限制,例如,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))

隐式参数(上下文参数)

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


0

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

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

在Scala编程的四个版本之后,人们仍然要区分两个重要的概念:功能和函数值,因为所有版本都没有给出清晰的解释,这仍然是一个问题。语言规范太复杂。我发现上述规则简单准确。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.