【问题标题】:Difference between method and function in ScalaScala中方法和函数的区别
【发布时间】:2011-02-01 12:27:26
【问题描述】:

我读过 Scala Functions另一个 Scala 之旅的一部分)。他在那篇文章中说:

方法和函数不是一回事

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

【问题讨论】:

标签: function scala methods


【解决方案1】:

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

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

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

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

最后,一个匿名函数是一个函数类型的实例(即特征FunctionN的一个实例)和一个方法值 是一样的!区别在于方法值是从方法创建的,或者通过后缀下划线(m _ 是对应于“函数声明”(defm 的方法值),或者通过名为 的进程eta-expansion,就像从方法到函数的自动转换。

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

以下由经验丰富的 Scala 程序员使用的术语对规范中的术语进行了一项更改:我们不说 函数声明,而是说 方法 。甚至方法声明。此外,我们注意到值声明变量声明也是实用的方法。

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

函数是包含FunctionX特征之一的对象,例如Function0Function1Function2等。它也可能包括PartialFunction ,实际上扩展了Function1

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

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

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

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

这告诉了我们所有关于它的知识。 函数有一个apply方法,它接收T1T2、...类型的N个参数TN,并返回 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 ""

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

【讨论】:

  • 这个解释很清楚。做得好。不幸的是,Odersky/Venners/Spoon 书和 Scala 规范都在某种程度上互换了使用“函数”和“方法”这两个词。 (他们最有可能在“方法”更清楚的地方说“函数”,但有时它也会以另一种方式发生,例如,规范的第 6.7 节涵盖了将方法转换为函数,被命名为“方法值”。呃.) 我认为当人们试图学习这门语言时,这些词的松散使用会导致很多混乱。
  • @Seth 我知道,我知道——PinS 是教我 Scala 的书。我在艰难的道路上学得更好,即 paulp 让我直截了当。
  • 很好的解释!我要补充一件事:当您将编译器对val f = m 的扩展引用为val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) } 时,您应该指出apply 方法中的this 不是指AnyRef 对象,而是指评估 val f = m _ 方法的对象(可以说是 outer this),因为 this 是闭包捕获的值之一(例如 return如下所述)。
  • @tldr Programming in Scala,由 Odersky 等人撰写。这是它的常见缩写(他们确实告诉我他们出于某种原因不太喜欢 PiS!:)
  • 如果您在 SO 答案中写了一篇文章,请在其顶部添加 TL;DR。
【解决方案2】:

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

【讨论】:

  • 那是因为 return 被闭包捕获了。
  • 我想不出有一次我想从函数“返回”到非本地范围。事实上,如果一个函数可以决定它想要更远地备份堆栈,我可以认为这是一个严重的安全问题。感觉有点像 longjmp,只是更容易意外出错。不过,我注意到 scalac 不会让我从函数中返回。这是否意味着这种憎恶已经从语言中消除了?
  • @root - 从for (a &lt;- List(1, 2, 3)) { return ... } 内部返回怎么样?这会导致关闭。
  • 嗯...嗯,这是一个合理的用例。仍然有可能导致可怕的难以调试的问题,但这确实将其置于更合理的环境中。
  • 老实说,我会使用不同的语法。让return 从函数中返回一个值,以及某种形式的escapebreakcontinue 从方法返回。
【解决方案3】:

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

Programming in Scala Second Edition. Martin Odersky - Lex Spoon - Bill Venners

【讨论】:

  • 一个函数可以作为 def 或 val/var 属于一个类。只有 def 是方法。
【解决方案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

检查以下Tutorial,它解释了如何通过其他示例传递其他差异,例如使用方法与函数的其他差异示例、使用函数作为变量、创建返回函数的函数

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 这有什么原因吗?
    【解决方案6】:

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

    功能: 它们基本上是一个对象。更准确地说,函数是具有 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 在对this question 的评论中指出的那样)等等。 一个简单的方法示例如下:

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

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

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

    【讨论】:

    • “将方法分配给函数”是什么意思?这是否意味着您现在拥有一个行为方式与该方法相同的对象?
    • @K.M : val f2 = m1 _ 等价于 val f2 = new Function1[Int, Int] { def m1(x: Int) = x + x };
    【解决方案7】:

    这是一个很棒的 post Rob Norris 解释了不同之处,这是一个 TL;DR

    Scala 中的方法不是值,但函数是。你可以构造一个函数,通过 η-expansion 委托给一个方法(由尾随的下划线触发)。

    具有以下定义:

    method 是用 def 定义的东西,value 是你可以分配给 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
    

    【讨论】:

      【解决方案8】:

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

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

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

      【讨论】:

        【解决方案9】:

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

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

        但是,Polymorphic function types #4672 在 dotty (Scala 3) 中解除了这些限制,例如,dotty 版本 0.23.0-RC1 启用了 following syntax

        类型参数

        def fmet[T](x: List[T]) = x.map(e => (e, e))
        val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
        

        隐式参数(context 参数)

        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

        【讨论】:

          【解决方案10】:

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

          Scala 和 C++ 都有 Fuction,但是在 JAVA 中,你必须用静态方法来模仿它们。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-06-09
            • 1970-01-01
            • 2015-01-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-01-16
            相关资源
            最近更新 更多