Scala 中方法和函数的区别

Difference between method and function in Scala

提问人:Anantha Kumaran 提问时间:3/27/2010 最后编辑:Peter MortensenAnantha Kumaran 更新时间:9/2/2022 访问量:83725

问:

我读了 Scala Functions(另一个 Scala 之旅的一部分)。在那篇博文中,他说:

方法和函数不是一回事

但他没有对此做出任何解释。他想说什么?

函数 Scala 方法

评论

2赞 Marcelo Cantos 4/17/2012
jim-mcbeath.blogspot.com/2009/05/......
3赞 jinglining 12/5/2014
我认为你可以从方法和函数有什么区别中得到一些东西
0赞 Josiah Yoder 6/25/2016
一个有良好答案的后续问题:Scala 中的函数与方法

答:

69赞 Ben Lings 3/27/2010 #1

方法和函数之间的一大实际区别是含义。 只从方法返回。例如:returnreturn

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赞 Daniel C. Sobral 3/28/2010
这是因为回报是由闭合捕获的。
6赞 root 7/2/2013
我想不出有一次我想从一个函数“返回”到非本地范围。事实上,如果一个函数可以决定它想要在堆栈上走得更远,我可以认为这是一个严重的安全问题。感觉有点像 longjmp,只是更容易意外出错。不过,我注意到 scalac 不会让我从函数中返回。这是否意味着这种可憎的东西已经从语言中被删除了?
2赞 Ben Lings 7/2/2013
@root - 从里面回来怎么样?它被脱糖到关闭。for (a <- List(1, 2, 3)) { return ... }
0赞 root 7/2/2013
嗯。。。嗯,这是一个合理的用例。仍然有可能导致可怕的难以调试的问题,但这确实将其置于更明智的环境中。
2赞 Ryan Leach 5/3/2016
老实说,我会使用不同的语法。从函数返回一个值,并从方法返回某种形式的 or 或。returnescapebreakcontinue
255赞 Daniel C. Sobral 3/28/2010 #2

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 形式的类型,它是标准库中特征的简写。匿名函数和方法值具有函数类型,函数类型可以用作、变量和函数声明和定义的一部分。事实上,它可以是方法类型的一部分。FunctionN

方法类型是非值类型。这意味着没有值 - 没有对象,没有实例 - 具有方法类型。如上所述,方法值实际上具有函数类型。方法类型是一个声明 - 除了它的主体之外的所有关于 a 的所有内容。defdef

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

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

最后,匿名函数是函数类型的实例(即特征的实例),方法是一回事!区别在于,方法值是从方法创建的,要么通过后缀下划线( 是对应于“函数声明”() 的方法值),要么通过称为 eta-expansion 的过程创建,这类似于从方法到函数的自动转换。FunctionNm _defm

这就是规范所说的,所以让我把它放在前面:我们不使用那个术语!这导致了所谓的“函数声明”和“匿名函数”(表达式)和“函数类型”之间的混淆,前者是程序的一部分(第4章 - 基本声明),后者是一个表达式,而“函数类型”则是一个类型,也是一种特征。

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

因此,鉴于上述术语的变化,这里是对这种区别的实际解释。

函数是包含特征之一的对象,例如 、 、 等。它可能也包括,这实际上扩展了 .FunctionXFunction0Function1Function2PartialFunctionFunction1

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

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

这个特征有一个抽象的方法(它也有一些具体的方法):

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

这告诉我们所有关于它的知识。函数有一个方法,它接收 T1T2、...、TN 类型的 N 个参数,并返回 .它在接收到的参数上是逆变的,在结果上是协变的。applyR

该方差意味着 a 是 的子类型。作为一个亚型意味着它可以代替它。人们可以很容易地看出,如果我要打电话并期待回来,上述两种类型中的任何一种都可以。Function1[Seq[T], String]Function1[List[T], AnyRef]f(List(1, 2, 3))AnyRef

现在,方法和函数的相似之处是什么?好吧,如果是一个函数并且是作用域的本地方法,那么两者都可以像这样调用:fm

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 将扩展它,假设 type 为 (Scala 2.7):m(List[Int])AnyRef

val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

在 Scala 2.8 上,它实际上使用类来减少类大小。AbstractFunction1

请注意,不能反过来转换 - 从函数转换为方法。

然而,方法有一个很大的优点(嗯,两个 - 它们可以稍微快一点):它们可以接收类型参数。例如,虽然上面必须指定它接收的类型(在示例中),但可以参数化它:fListList[Int]m

def m[T](l: List[T]): String = l mkString ""

我认为这几乎涵盖了所有内容,但我很乐意通过回答可能仍然存在的任何问题来补充这一点。

评论

28赞 Seth Tisue 5/21/2010
这个解释很清楚。干的好。不幸的是,Odersky/Venners/Spoon 的书和 Scala 规范在某种程度上互换使用“函数”和“方法”这两个词。(他们最有可能说“函数”,而“方法”会更清楚,但有时它也会以另一种方式发生,例如,规范的第 6.7 节,其中介绍了将方法转换为函数,被命名为“方法值”。呃。我认为,当人们试图学习这门语言时,这些词的松散使用会导致很多混乱。
4赞 Daniel C. Sobral 5/21/2010
@Seth我知道,我知道 PinS 是教会我 Scala 的书。我以艰难的方式学得更好,即,保罗让我直截了当。
5赞 Holger Peine 1/23/2013
很好的解释!我有一件事要补充:当你引用编译器的扩展时,你应该指出,方法的内部不是指对象,而是指其方法被计算的对象(外部,可以这么说),因为它是闭包捕获的值之一(例如 如下所述)。val f = mval f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }thisapplyAnyRefval f = m _thisthisreturn
5赞 Daniel C. Sobral 6/19/2013
@tldr Programming in Scala,作者:Odersky et all。这是它的常见缩写(他们确实告诉我,出于某种原因,他们不太喜欢PiS:)!
2赞 MaxNevermind 3/29/2018
如果您在 SO 答案中写一篇文章,请添加 TL;DR 到它的顶部。
13赞 eptx 5/4/2011 #3

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

评论

6赞 corazza 3/10/2014
这是有原因的吗?
32赞 anish 11/1/2013 #4

假设你有一个列表

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

查看以下教程,该教程解释了通过示例传递其他差异,例如使用方法与函数的差异的其他示例,使用函数作为变量,创建返回函数的函数

44赞 jamlhet 7/2/2014 #5

功能可以使用参数列表调用函数以生成 结果。函数具有参数列表、正文和结果类型。 作为类、特征或单例对象成员的函数是 称为方法。在其他函数中定义的函数称为 本地函数。结果类型为 Unit 的函数称为过程。 源代码中的匿名函数称为函数文本。 在运行时,函数文本被实例化为名为 函数值。

Scala 编程第二版。 Martin Odersky - Lex Spoon - 比尔·维纳斯

评论

1赞 Josiah Yoder 8/19/2015
函数可以作为 def 或 val/var 属于类。只有 def 是方法。
12赞 Mehran 2/23/2018 #6

这里有一篇很好的文章,我的大部分描述都来自它。 关于我的理解,只是对函数和方法的简短比较。希望对您有所帮助:

功能: 它们基本上是一个对象。更准确地说,函数是具有 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>

评论

0赞 K. M 8/17/2018
“将方法分配给函数”是什么意思?这是否只是意味着您现在有一个行为与方法相同的对象?
0赞 sasuke 3/8/2020
@K.M : val f2 = m1 _ 等价于 val f2 = new Function1[Int, Int] { def m1(x: Int) = x + x };
4赞 Valy Dia 4/25/2019 #7

这是 Rob Norris 的一篇很棒的文章,解释了其中的区别,这是一个 TL;博士

Scala 中的方法不是值,而是函数。您可以构造一个函数,该函数通过η扩展(由尾随下划线 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赞 Mario Galic 2/4/2020 #8

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

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

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

2赞 Ying 2/10/2020 #9

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

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

在《Programming in Scala》出版了四个版本之后,人们仍然很难区分两个重要的概念:函数和函数值,因为所有版本都没有给出明确的解释。语言规范太复杂了。我发现上面的规则简单而准确。

-2赞 GPopat 10/16/2021 #10

方法对对象进行操作,但函数不对对象进行操作。

Scala 和 C++ 具有功能,但在 JAVA 中,您必须使用静态方法模仿它们。

0赞 Alin Gabriel Arhip 6/13/2022 #11

方法属于一个对象(通常是 ,或者你在其中定义它),而函数本身就是一个值,因为在 Scala 中,每个值都是一个对象,因此,函数就是一个对象classtraitobject

例如,给定以下方法和函数:

def timesTwoMethod(x :Int): Int = x * 2
def timesTwoFunction = (x: Int) => x * 2

第二个是类型的对象(语法糖 )。defInt => IntFunction1[Int, Int]

Scala 制作了函数对象,因此它们可以用作第一类实体。这样,您可以将函数作为参数传递给其他函数。

但是,Scala 也可以通过一种称为 Eta 扩展的机制将方法视为函数。

例如,在 上定义的高阶函数接收另一个函数作为其唯一参数。接下来的两行是等效的:mapListf: A => B

List(1, 2, 3).map(timesTwoMethod)
List(1, 2, 3).map(timesTwoFunction)

当编译器在需要函数的地方看到给定值时,它会自动将该方法转换为等效函数。def

0赞 gifa 8/16/2022 #12

这种差异是微妙的,但很大,它与正在使用的类型系统有关(除了来自面向对象或函数范式的命名法)。

当我们谈论一个函数时,我们谈论的是 Function 类型:它是一个类型,它的实例可以作为输入或输出传递给其他函数(至少在 Scala 的情况下)。

当我们谈论一个方法(一个类的)时,我们实际上是在谈论它所属的类所表示的类型:也就是说,该方法只是一个更大类型的一个组件,不能单独传递。它必须与它所属类型的实例(即类的实例)一起传递。