在Scala中,def foo = {}和def foo() = {}有什么区别?

56

给出以下用于在Scala中定义函数的结构体,请解释它们之间的区别以及其影响是什么。

def foo = {}

对比。

def foo() = {}

更新

感谢快速回复,非常好的解答。对我来说仅剩的问题是:

如果我省略括号,是否仍有办法传递函数?这是我在repl中得到的结果:

scala> def foo = {}
foo: Unit

scala> def baz() = {}
baz: ()Unit

scala> def test(arg: () => Unit) = { arg }
test: (arg: () => Unit)() => Unit

scala> test(foo)
<console>:10: error: type mismatch;
 found   : Unit
 required: () => Unit
              test(foo)
                   ^

scala> test(baz)
res1: () => Unit = <function0>

更新 2012-09-14

我发现了一些类似的问题:

  1. 带括号和不带括号的函数之间的区别
  2. 没有参数的Scala方法

2
def 关键字定义的是一个方法,而不是函数,尽管它们之间的区别可能很微妙。可以将其与 Java 中的 int vs Integer 相类比:前者可以自动装箱为后者。 - Kevin Wright
4个回答

41

如果在方法的定义中包含括号,则调用该方法时可以选择省略它们。如果在定义中省略了它们,则在调用该方法时不能使用它们。

scala> def foo() {}
foo: ()Unit

scala> def bar {}
bar: Unit

scala> foo

scala> bar()
<console>:12: error: Unit does not take parameters
       bar()
          ^

此外,您可以使用高阶函数做类似的事情:

scala> def baz(f: () => Unit) {}
baz: (f: () => Unit)Unit

scala> def bat(f: => Unit) {}
bat: (f: => Unit)Unit

scala> baz(foo)    

scala> baz(bar)
<console>:13: error: type mismatch;
 found   : Unit
 required: () => Unit
       baz(bar)
           ^
scala> bat(foo)

scala> bat(bar)  // both ok

这里的baz只能使用foo()而不能使用bar。我不知道这有什么用,但它确实显示了类型是不同的。


有趣。如果Scala程序员遵循Eugene Yokota引用的理由/设计标准,那么这意味着“baz”仅接受可能具有副作用的函数,而“bat”也接受没有副作用的函数。不确定是否有任何实际应用;我的直觉是能够编写仅接受没有副作用的函数的函数可能更有用(因为它执行某种并行处理,而副作用可能会导致问题),但建议提供的函数应具有副作用可能有其用处。 - Theodore Murdock
如果在定义中包含括号,则在调用方法时可以选择省略它们。如果在定义中省略它们,则在调用方法时无法使用它们。这有助于解释我对http://docs.scala-lang.org/tutorials/scala-for-java-programmers.html中的评论“方法re和im的一个小问题是,为了调用它们,必须在它们的名称后面放置一对空括号”的疑问。实际上,他们给出的使用方法“def re() = real”可以在有或没有括号的情况下都能正常工作。谢谢。 - Matthew Cornell
2
所以基本上,如果在定义中省略了括号,则在调用该方法时必须省略它们。此外,当方法具有副作用时,永远不应省略括号,因为这看起来像属性调用。 - 0x6C38

40

让我复制一下我在一个重复的问题上发布的答案:

Scala 2.x中的零元方法可以带有或不带有括号()。这用于向用户发出信号,表明该方法具有某种副作用(如打印到标准输出或销毁数据),而没有使用括号的方法可以稍后实现为val

参见Programming in Scala

这种参数列表为空的方法在Scala中相当常见。相比之下,定义了空括号的方法,例如def height():Int,称为空括号方法。推荐的惯例是:仅当没有参数并且该方法仅通过读取包含对象的字段来访问可变状态(特别是它不改变可变状态)时,请使用无参数方法。

这个约定支持统一访问原则...

总之,在Scala中,鼓励将不需要参数且没有副作用的方法定义为无参数方法,即省略空括号。另一方面,永远不要定义没有括号的具有副作用的方法,因为那样调用该方法将看起来像字段选择。

术语

关于零元方法存在一些混淆的术语,因此我会在这里创建一个表:

Programming in Scala scala/scala专业术语
def foo: Int 无参数方法 nullary方法
def foo(): Int 空括号方法 nilary方法

说“nullary method”听起来很酷,但是经常有人说错,读者也会感到困惑,所以我建议坚持使用parameterless和empty-paren方法,除非你在一个已经使用这些术语的拉取请求中。

在Scala 2.13或3.0中,()不再是可选的

The great () insert中,Martin Odersky修改了Scala 3,要求使用()调用定义为()的方法。这在Scala 3迁移指南中有所记录:

自动应用是调用没有传递空参数列表的nullary方法的语法。

注意: 迁移文档术语有误。应该是:

自动应用是调用没有传递空参数列表的空括号(或“nilary”)方法的语法。

Scala 2.13遵循Scala 3.x并在Eta-expand 0-arity method if expected type is Function0中弃用了空括号方法的自动应用。一个值得注意的例外是Java定义的方法。我们仍然可以调用像toString这样的Java方法而不需要()


3
感谢您解释了“what”背后的“why”。 - RustyTheBoyRobot
1
另一方面,你永远不应该定义一个具有副作用的方法而没有括号,因为这样调用该方法会看起来像字段选择。但是,使用括号定义的方法仍然可以在不使用括号的情况下调用,所以我不同意这个论点。 - Andrew McKinlay
@AndrewMcKinlay 是的,这有点奇怪,特别是当你注意到访问可变字段一种副作用时。 - aij

7
为了回答您的第二个问题,只需添加一个_
scala> def foo = println("foo!")
foo: Unit

scala> def test(arg: () => Unit) = { arg }
test: (arg: () => Unit)() => Unit

scala> test(foo _)
res10: () => Unit = <function0>

scala> test(foo _)()
foo!

scala>            

0
我建议始终从函数开始定义,例如:
def bar {}

只有在被迫的情况下,才需要将其更改为:

def bar() {}

原因:让我们从可能使用的角度考虑这两个函数。它们如何被调用,以及它们可以被传递到哪里。
我根本不会称之为一个函数:
def bar {}

它可以被调用为:

bar

但不作为一个函数:

bar()

当我们使用按名称调用的参数定义高阶函数时,可以使用此条形代码:

def bat(f: => Unit) {
    f //you must not use (), it will fail f()
}

我们应该记住,=> Unit - 不是一个函数。你绝对不能像处理函数一样处理thunk,因为你不能选择将其视为要存储或传递的Function值。你只能触发实际参数表达式(任意数量)的求值。 Scala:在花括号之间传递代码块的函数 使用()定义的函数具有更大的使用范围。它可以在完全相同的上下文中与bar一样使用:
def foo() = {}
//invokation:
foo
//or as a function:
foo()

它可以作为一个带有按名称调用参数的函数的参数传递:

bat(foo)

此外,如果我们定义一个高阶函数,它不接受按名称调用的参数,而是一个真正的函数:
def baz(f: () => Unit) {}

我们也可以将foo传递给baz

baz(foo)

正如我们所看到的,像foo这样的标准函数具有更大的使用范围。但是,使用没有定义()的函数以及定义接受按名称调用参数的高阶函数,让我们使用更清晰的语法。

如果您不尝试实现更好、更可读的代码,或者如果您需要将代码片段传递给既定为按名称调用参数的函数又要传递给真正的函数定义,则将函数定义为标准函数:

def foo() {}

如果你更喜欢编写更清晰易读的代码,而且你的函数没有副作用,请定义一个函数如下:

def bar {}

另外尝试定义你的高阶函数以接受按名称调用的参数,而不是函数。 只有在被迫的情况下,才使用前面的选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接