我是Scala的新手,并且已经看到很多方法来定义函数,但是找不到关于差异的明确解释,以及何时使用哪种形式.
以下函数定义之间的主要区别是什么?
用'='
def func1(node: scala.xml.Node) = {
print(node.label + " = " + node.text + ",")
}
Run Code Online (Sandbox Code Playgroud)没有'='
def func2 (node: scala.xml.Node) {
print(node.label + " = " + node.text + ",")
}
Run Code Online (Sandbox Code Playgroud)用'=>'
def func3 = (node: scala.xml.Node) => {
print(node.label + " = " + node.text + ",")
}
Run Code Online (Sandbox Code Playgroud)作为变种
var func4 = (node: scala.xml.Node) => {
print(node.label + " = " + node.text + ",")
}
Run Code Online (Sandbox Code Playgroud)没有块
def func5 (node: scala.xml.Node) = print(node.label + " = " + node.text + ",")
Run Code Online (Sandbox Code Playgroud)当用作回调时,它们似乎都编译并呈现相同的结果
xmlNodes.iterator.foreach(...)
Run Code Online (Sandbox Code Playgroud)
Rex*_*err 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
Run Code Online (Sandbox Code Playgroud)
通常编写的函数A => B是其中一个类的子Function类,例如Function1[A,B].因为这个类有一个apply方法,当你只使用没有方法名称的parens时Scala神奇地调用它,它看起来像一个方法调用 - 它是,除了它是对该Function对象的调用!所以,如果你写
def f3 = (s: String) => println(s)
Run Code Online (Sandbox Code Playgroud)
那么你所说的是" f3应该创建一个Function1[String,Unit]具有apply类似方法的实例def apply(s: String) = println(s)".所以如果你说f3("Hi"),这是第一次调用f3创建函数对象,然后调用该apply方法.
每次想要使用它时创建函数对象都相当浪费,因此将函数对象存储在var中更有意义:
val f4 = (s: String) => println(s)
Run Code Online (Sandbox Code Playgroud)
这包含def(方法)将返回的同一函数对象的一个实例,因此您不必每次都重新创建它.
人们对公约的不同: Unit = ...和{ }.就个人而言,我编写所有返回Unit没有等号的方法- 这表明该方法几乎肯定无用,除非它有某种副作用(变异变量,执行IO等).此外,我通常只在需要时使用大括号,因为有多个语句或因为单个语句太复杂我想要一个视觉辅助来告诉我它的结束位置.
应该随时使用方法,以及方法.只要您想将函数对象传递给其他方法来使用它们,就应该创建函数对象(或者应该在您希望能够应用函数时将其指定为参数).例如,假设您希望能够缩放值:
class Scalable(d: Double) {
def scale(/* What goes here? */) = ...
}
Run Code Online (Sandbox Code Playgroud)
你可以提供一个常数乘数.或者你可以提供一些东西来增加和增加一些东西.但最灵活,你只是要求从任意函数Double来Double:
def scale(f: Double => Double) = f(d)
Run Code Online (Sandbox Code Playgroud)
现在,也许您已经了解了默认比例.这可能根本没有扩展.所以你可能想要一个带a Double和返回相同的函数Double.
val unscaled = (d: Double) => d
Run Code Online (Sandbox Code Playgroud)
我们将函数存储在a中,val因为我们不想一遍又一遍地创建它.现在我们可以将此函数用作默认参数:
class Scalable(d: Double) {
val unscaled = (d: Double) => d
def scale(f: Double => Double = unscaled) = f(d)
}
Run Code Online (Sandbox Code Playgroud)
现在,我们可以同时调用x.scale并x.scale(_*2)与x.scale(math.sqrt)他们都会工作.
是的,字节码有差异.是的,有指导方针.
With =:这声明了一个接受参数并返回右侧块中最后一个表达式的方法,该表达式具有Unit此处的类型.
不使用=:这声明了一个没有返回值的方法,即返回类型总是如此Unit,而不管右侧块中最后一个表达式的类型是什么.
With =>:这声明了一个返回类型的函数对象的方法scala.xml.Node => Unit.每次调用此方法时func3,都将在堆上构造一个新的函数对象.如果你编写func3(node),你将首先调用func3哪个返回函数对象,然后调用该apply(node)函数对象.这比直接调用普通方法慢,如情况1和2.
作为a var:这声明了一个变量并创建一个函数对象,如3.,但函数对象只创建一次.在大多数情况下,使用它来调用函数对象比仅仅普通方法调用慢(可能不是由JIT内联),但至少你不重新创建对象.如果您想避免某人重新分配变量的危险func4,请使用val或lazy val替代.
当块仅包含单个表达式时,这是1的语法糖.
请注意,如果您使用表格1,2和5与高阶foreach方法,斯卡拉仍然会创建一个函数对象,调用func1,func2或func5含蓄,并传递到foreach(它不会使用方法手柄或像水木清华那,至少在当前版本中不是这样).在这些情况下,生成的代码将大致对应于:
xmlNodes.iterator.foreach((node: scala.xml.Node) => funcX(node))
Run Code Online (Sandbox Code Playgroud)
所以,指南是 - 除非你每次使用相同的函数对象,只需创建一个普通的方法,如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