在Scala中编写函数的以下方法有什么区别?

时间:2012-06-21 14:51:23

标签: scala

我是Scala的新手,并且已经看到很多方法来定义函数,但是找不到关于差异的明确解释,以及何时使用哪种形式。

以下功能定义之间有哪些主要区别?

  1. 使用'='

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

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

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

    var func4 = (node: scala.xml.Node) => {
        print(node.label + " = " + node.text + ",")
    }
    
  5. 没有阻止

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

    的回调时,它们似乎都编译并呈现相同的结果
        xmlNodes.iterator.foreach(...)
    
    • 每个生成的字节码有什么不同吗?
    • 使用哪种表格时是否有任何指导?

4 个答案:

答案 0 :(得分:19)

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

大括号和等于

用等号定义的方法返回一个值(无论最后一个值是什么)。仅使用大括号定义的方法返回Unit。如果你使用等于,但最后一件事是评估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在你只使用没有方法名称的parens时会神奇地调用它,它看起来像一个方法调用 - 除了它是对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? */) = ...
}

您可以提供常数乘数。或者你可以提供一些东西来增加和增加一些东西。但最灵活的是,你只需要DoubleDouble的任意函数:

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

现在,您可能已经了解了默认比例。这可能根本没有扩展。因此,您可能需要一个采用Double的函数并返回相同的Double

val unscaled = (d: Double) => d

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

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

现在我们可以同时调用x.scalex.scale(_*2)以及x.scale(math.sqrt),这些都可以正常工作。

答案 1 :(得分:5)

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

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

  2. 没有=:这声明了一个没有返回值的方法,也就是说,返回类型总是Unit,而不管最后一个表达式的类型是什么右手边是。

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

  4. 作为var:这声明了一个变量并创建了一个函数对象,如3.,但函数对象只创建一次。使用它来调用函数对象在大多数情况下比只是一个普通的方法调用慢(可能不是由JIT内联),但至少你不重新创建对象。如果您想避免某人重新分配变量func4的危险,请改用vallazy val

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

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

    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

答案 2 :(得分:2)

嗯,1,2和5不是函数,它们是方法,它们与函数根本不同:方法属于< / em>对象并不是它们自己的对象,而函数对象。

1,2和5也完全相同:如果你只有一个语句,那么你不需要花括号来分组几个语句,ergo 5与1相同。离开{{1} } sign是用于声明返回类型=的语法糖,但Unit也是1和5的推断返回类型,因此2与1和5相同。

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

答案 3 :(得分:1)

1-2。当你扔掉等号时,你的功能就变成了程序(返回单位,或者什么也没有) 3.在第三种情况下,您定义了一个函数scala.xml.Node => Unit,它返回一个函数 4.同样,但您已为变量分配了一些函数scala.xml.Node => UnitDifferences between these three ways of defining a function in Scala中解释的差异 5.与1相比没有区别。但你不能写那样的多行语句。