8

I am new to Scala, and have seen many ways to define a function but could not find a clear explanation on the differences, and when to use which form.

What are the main differences between the following function definitions?

  1. With '='

    def func1(node: scala.xml.Node) = {
        print(node.label + " = " + node.text + ",")
    }
    
  2. Without '='

    def func2 (node: scala.xml.Node) {
        print(node.label + " = " + node.text + ",")
    }
    
  3. With '=>'

    def func3 = (node: scala.xml.Node) => {
        print(node.label + " = " + node.text + ",")
    }
    
  4. As a var

    var func4 = (node: scala.xml.Node) => {
        print(node.label + " = " + node.text + ",")
    }
    
  5. Without a block

    def func5 (node: scala.xml.Node) = print(node.label + " = " + node.text + ",")  
    

They all seem to compile and render the same result when used as a callback for

    xmlNodes.iterator.foreach(...)
  • Is there any difference in the bytecode each generate?
  • Are there any guidlines when to use which form?
4

4 回答 4

19

这些问题中的每一个都已在本网站的其他地方得到解答,但我认为没有任何东西可以一起处理它们。所以:

大括号和等号

用等号定义的方法返回一个值(无论最后的结果是什么)。只用大括号定义的方法返回Unit。如果您使用 equals 但最后一件事评估为Unit,则没有区别。如果是等号后的单个语句,则不需要大括号;这对字节码没有影响。所以 1.、2. 和 5. 本质上是相同的:

def f1(s: String) = { println(s) }     // println returns `Unit`
def f2(s: String) { println(s) }       // `Unit` return again
def f5(s: String) = println(s)         // Don't need braces; there's only one statement

函数与方法

一个函数,通常写成A => B,是其中一个类的子Function类,例如Function1[A,B]。因为这个类有一个apply方法,当你只使用没有方法名的括号时,Scala 会神奇地调用它,它看起来像一个方法调用——它确实是,除了它是对该Function对象的调用!所以如果你写

def f3 = (s: String) => println(s)

那么您要说的是“f3应该创建一个实例,Function1[String,Unit]该实例的apply方法看起来像def apply(s: String) = println(s)”。所以如果说f3("Hi"),这是先调用f3创建函数对象,再调用apply方法。

每次要使用它时都创建函数对象是相当浪费的,因此将函数对象存储在 var 中更有意义:

val f4 = (s: String) => println(s)

这包含def(方法)将返回的同一函数对象的一个​​实例,因此您不必每次都重新创建它。

什么时候用什么

人们对: Unit = ...和的约定存在分歧{ }。就我个人而言,我编写了所有Unit不带等号返回的方法——这表明该方法几乎肯定是无用的,除非它具有某种副作用(改变变量、执行 IO 等)。此外,我通常只在需要时使用大括号,因为有多个语句,或者因为单个语句非常复杂,我需要一个视觉辅助工具来告诉我它在哪里结束。

任何时候都应该使用方法,嗯,一种方法。函数对象应在您希望将它们传递给其他方法以使用它们的任何时候创建(或者应该在您希望能够应用函数的任何时候指定为参数)。例如,假设您希望能够缩放一个值:

class Scalable(d: Double) {
  def scale(/* What goes here? */) = ...
}

你可以提供一个常数乘数。或者你可以提供一些要添加的东西和一些要增加的东西。但最灵活的是,您只需要从Doubleto请求一个任意函数Double

def scale(f: Double => Double) = f(d)

现在,也许您对默认比例有所了解。这可能根本没有缩放。所以你可能想要一个接受 aDouble并返回相同的函数Double

val unscaled = (d: Double) => d

我们将函数存储在 a 中是val因为我们不想一遍又一遍地创建它。现在我们可以使用这个函数作为默认参数:

class Scalable(d: Double) {
  val unscaled = (d: Double) => d
  def scale(f: Double => Double = unscaled) = f(d)
}

现在我们可以同时调用x.scaleandx.scale(_*2)并且x.scale(math.sqrt)它们都可以工作。

于 2012-06-21T15:13:18.850 回答
5

是的,字节码存在差异。是的,有指导方针。

  1. With =:这声明了一个方法,该方法接受一个参数并返回右侧块中的最后一个表达式,该表达式具有Unit此处的类型。

  2. 没有:这声明了一个没有返回值的方法,也就是说,无论右侧块中最后一个表达式的类型是什么,=返回类型始终为 。Unit

  3. With =>:这声明了一个返回类型为函数对象scala.xml.Node => Unit的方法。每次调用此方法func3时,都会在堆上构造一个新的函数对象。如果您编写func3(node),您将首先调用func3返回函数对象,然后调用该apply(node)函数对象上的 。这比在情况 1. 和 2 中直接调用普通方法要慢。

  4. As a var:这声明了一个变量并创建了一个函数对象,如 3. 中一样,但函数对象只创建一次。在大多数情况下,使用它来调用函数对象比简单的方法调用要慢(可能不会被 JIT 内联),但至少您不会重新创建对象。如果您想避免有人重新分配变量的危险func4,请改用 aval或 a lazy val

  5. 这是 1. 当块只包含一个表达式时的语法糖。

请注意,如果您将 1.、2. 和 5. 形式与高阶foreach方法一起使用,Scala 仍将创建一个调用或隐式调用func1的函数对象,并将其传递给(它不会使用方法句柄或类似至少在当前版本中不是这样)。在这些情况下,生成的代码将大致对应于:func2func5foreach

xmlNodes.iterator.foreach((node: scala.xml.Node) => funcX(node))

因此,指导方针是 - 除非您每次都使用相同的函数对象,否则只需像 1.、2. 或 5 中那样创建一个普通方法。无论如何,它都会被提升为一个函数对象,这是需要的。如果你意识到这会生成很多对象,因为调用这样的方法经常发生,你可能希望使用表格 4. 进行微优化,而不是确保函数对象foreach只被创建一次。

在 1.、2. 和 5. 之间做出决定时,一个准则是 - 如果您只有一个陈述,请使用表格 5。

否则,如果返回类型为Unit,则使用def foo(): Unit = {公共 API 的形式,以便客户端快速查看您的代码并清楚地看到返回类型。def foo() {为返回类型为私有的方法使用该表单Unit,以方便您使用更短的代码。但这只是关于风格的一个特定准则。

有关更多信息,请参阅:http ://docs.scala-lang.org/style/declarations.html#methods

于 2012-06-21T15:03:46.523 回答
2

好吧,1、2 和 5 根本不是函数它们是方法,它们与函数有根本的不同:​​方法属于对象,它们本身不是对象,而函数对象。

1, 2, 5 也完全一样:如果你只有一个语句,那么你不需要花括号来组合几个语句,所以 5 与 1 相同。省略=符号是声明 a 的语法糖的返回类型Unit,但Unit也是 1 和 5 的推断返回类型,因此 2 与 1 和 5 相同。

3 是一个方法,当被调用时,它会返回一个函数。4 是指向函数的变量。

于 2012-06-21T15:06:14.237 回答
1

1-2。当你扔掉等号时,你的函数就变成了过程(返回单位,或者什么都没有)。
3. 在第三种情况下,您定义了一个函数scala.xml.Node => Unit,它返回一个函数。
4. 相同,但您已scala.xml.Node => Unit为变量分配了一些函数。在 Scala 5 中定义函数的这三种方式之间的差异中解释了差异
。没有区别,与 1 相比。但是你不能写这样的多行语句。

于 2012-06-21T15:03:57.627 回答