55

给定以下用于在 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 方法
4

4 回答 4

40

如果在定义中包含括号,则可以在调用方法时选择省略它们。如果在定义中省略它们,则在调用方法时将无法使用它们。

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。这个有什么用,不知道。但它确实表明类型是不同的。

于 2011-09-13T23:15:26.193 回答
40

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

可以使用或不使用括号定义 Scala 2.x 的 0-arity 方法()。这用于向用户表明该方法具有某种副作用(例如打印到标准输出或破坏数据),而不是没有的,稍后可以将其实现为val.

请参阅Scala 编程

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

该约定支持统一访问原则 [...]

总而言之,Scala 鼓励将不带参数且没有副作用的方法定义为无参数方法,即省略空括号。另一方面,你永远不应该定义一个没有括号的有副作用的方法,因为那样的话,对该方法的调用看起来就像一个字段选择。

术语

关于 0-arity 方法有一些令人困惑的术语,所以我将在这里创建一个表:

在 Scala 中编程 斯卡拉/斯卡拉行话
def foo: Int 无参数方法 无效方法
def foo(): Int 空括号方法 方法

我说“nullary method”听起来很酷,但经常有人说错了,读者也会感到困惑,所以我建议坚持使用无参数和空括号方法,除非你在一个人们已经在使用的拉取请求中行话。

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

Great() 插入中,Martin Odersky 对 Scala 3 进行了更改,要求()调用用(). 这在Scala 3 迁移指南中记录为:

自动应用程序是在不传递空参数列表的情况下调用空方法的语法。

注意:迁移文档的术语错误。它应该读作:

自动应用程序是在不传递空参数列表的情况下调用空括号(或“nilary”)方法的语法。

Scala 2.13,紧随 Scala 3.x 并弃用了Eta-expand 0-arity 方法中空括号方法的自动应用,如果预期类型是 Function0。此规则的一个显着例外是 Java 定义的方法。我们可以继续调用 Java 方法,例如toStringwithout ().

于 2011-09-30T05:16:13.697 回答
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>            
于 2015-11-13T20:13:38.850 回答
0

我建议始终使用以下函数开始定义:

def bar {}

并且仅在您被迫将其更改为:

def bar() {}

原因:让我们从可能的使用角度考虑这两个函数。他们如何被告知以及他们可以在哪里通过。

我根本不会称其为函数:

def bar {}

它可以被调用为:

bar

但不是作为一个函数:

bar()

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

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

我们应该记住,这=> Unit- 甚至不是一个函数。您绝对不能像使用函数一样使用 thunk,因为您不能选择将其视为要存储或传递的函数值。您只能触发对实际参数表达式(任意数量)的评估。 Scala:将函数作为大括号之间的代码块传递

定义为 的函数()具有更大的使用范围。它可以在相同的上下文中完全使用,如bar

def foo() = {}
//invokation:
foo
//or as a function:
foo()

它可以通过名称调用参数传递给函数:

bat(foo)

此外,如果我们定义一个高阶函数,它接受的不是按名称调用参数,而是一个真正的函数:

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

我们也可以传递foobaz

baz(foo)

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

如果您不尝试归档更好、更易读的代码,或者如果您需要能够将您的代码既传递给使用按名称调用参数定义的函数,又传递给使用真实函数定义的函数,那么定义您的标准功能:

def foo() {}

如果您喜欢编写更清晰易读的代码,并且您的函数没有副作用,请将函数定义为:

def bar {}

PLUS 尝试定义您的高阶函数以接受按名称调用的参数,而不是函数。仅当您被迫时,才在这种情况下使用上一个选项。

于 2018-02-28T11:50:41.433 回答