36

我了解零参数方法和无参数方法之间的区别,但我并不真正了解的是使无参数方法成为必要的语言设计选择。

我能想到的缺点:

  • 这很令人困惑。每周或每两周,这里或 Scala 邮件列表上都会有关于它的问题。
  • 情况很复杂; 我们还必须区分() => X=> X
  • 它是模棱两可的:是x.toFoo(y)指它所说的,还是x.toFoo.apply(y)?(答案:这取决于x'toFoo方法的重载和 ' 方法上的重载Fooapply但如果发生冲突,在您尝试调用它之前您不会看到错误。)
  • 它弄乱了运算符风格的方法调用语法:在链接方法时或在末尾避免分号干扰时,没有符号可以代替参数。使用零参数方法,您可以使用空参数列表()

目前,您不能在一个类中同时定义两者:您会收到一条错误消息,指出该方法已定义。它们也都转换为Function0.

为什么不直接制作方法def foodef foo()完全相同的东西,并允许在有或没有括号的情况下调用它们?它的优点是什么?

4

5 回答 5

31

咖喱,这就是为什么

Daniel 在解释为什么需要无参数方法方面做得很好。我将解释为什么将它们与零参数方法区别开来。

许多人将无参数函数和零参数函数之间的区别视为某种模糊形式的语法糖。事实上,它纯粹是 Scala 如何支持柯里化的产物(为了完整起见,请参阅下文,以更全面地解释柯里化是什么,以及为什么我们都这么喜欢它)。

形式上,一个函数可以有零个或多个参数列表,每个参数列表有零个或多个参数。
这意味着以下内容是有效的:def a, def b(),还有人为的def c()()等等def d(x: Int)()()(y: Int)......

一个函数def foo = ???有零个参数列表。一个函数def bar() = ???只有一个参数列表,参数为零。引入将这两种形式混为一谈的附加规则会破坏柯里化作为一种​​一致的语言特征def a在形式上等同于def b()def c()()两者;def d(x: Int)()()(y: Int)将等同于def e()(x: Int)(y: Int)()()

一种与柯里化无关的情况是在处理 Java 互操作时。Java 不支持柯里化,因此为像"test".length()(直接调用java.lang.String#length())这样的零参数方法引入语法糖也可以调用为"test".length.

柯里化的快速解释

Scala 支持一种名为“currying”的语言特性,以数学家 Haskell Curry 命名。
Currying 允许您使用多个参数列表定义函数,例如:

def add(a: Int)(b: Int): Int = a + b
add(2)(3) // 5

这很有用,因为您现在可以inc根据 的部分应用来定义add

def inc: Int => Int = add(1)
inc(2) // 3

柯里化通常被视为通过库引入控制结构的一种方式,例如:

def repeat(n: Int)(thunk: => Any): Unit = (1 to n) foreach { _ => thunk }

repeat(2) {
  println("Hello, world")
}

// Hello, world
// Hello, world

回顾一下,看看如何repeat打开另一个使用柯里化的机会:

def twice: (=> Any) => Unit = repeat(2)

twice {
  println("Hello, world")
}

// ... you get the picture :-)
于 2013-04-26T20:35:41.593 回答
13

关于 ML 定期出现的问题的一件好事是有定期的答案。

谁能抗拒一个叫做“我们怎么了?”的话题。

https://groups.google.com/forum/#!topic/scala-debate/h2Rej7LlB2A

来自:martin odersky 日期:2012 年 3 月 2 日,星期五,下午 12:13 主题:回复:[scala-debate] 我们怎么了...

有些人认为“我们错了”的是,我们正在努力让 Java 习惯用法在 Scala 中顺利运行。主要的事情是说 def length() 和 def length 是不同的,而且,抱歉,String 是一个 Java 类,所以你必须编写 s.length(),而不是 s.length。我们非常努力地通过承认从 s.length 到 s.length() 的自动转换来掩盖它。这是有问题的。概括这一点,以便在类型系统中识别这两者将是注定要失败的肯定方式。那么你如何消除歧义:

type Action = () => () def foo: Action

那么 foo 是 Action 还是 () 类型?那么 foo() 呢?

马丁

我最喜欢那个帖子里的保罗小说:

On Fri, Mar 2, 2012 at 10:15 AM, Rex Kerr <ich...@gmail.com> wrote:

>This would leave you unable to distinguish between the two with 
>structural types, but how often is the case when you desperately 
>want to distinguish the two compared to the case where distinguishing 
>between the two is a hassle?


/** Note to maintenance programmer: It is important that this method be
 *  callable by classes which have a 'def foo(): Int' but not by classes which
 *  merely have a 'def foo: Int'.  The correctness of this application depends
 *  on maintaining this distinction.
 *  
 *  Additional note to maintenance programmer: I have moved to zambia.
 *  There is no forwarding address.  You will never find me.
 */
def actOnFoo(...)

因此,该功能的潜在动机是生成这种 ML 线程。

还有一点谷歌学:

2010 年 4 月 1 日星期四晚上 8:04,Rex Kerr <[hidden email]> 写道: 2010 年 4 月 1 日星期四下午 1:00,richard emberson <[hidden email]> 写道:

我假设“def getName:String”与“def getName():String”相同

不,实际上,他们不是。尽管它们都调用了没有参数的方法,但一种是“具有零参数列表的方法”,而另一种是“具有一个空参数列表的方法”。如果您想更加困惑,请尝试 def getName()(): String (并使用该签名创建一个类)!

Scala 将参数表示为列表的列表,而不仅仅是列表,并且

列表()!=列表(列表())

这是一种奇怪的烦恼,特别是因为两者之间几乎没有区别,而且两者都可以自动转换为函数签名 () => String。

真的。事实上,无参数方法和具有空参数列表的方法之间的任何混淆完全是由于 Java 互操作。它们应该是不同的,但是处理 Java 方法将太痛苦了。你能想象每次取一个字符串的长度时都必须写 str.length() 吗?

干杯

于 2012-09-09T15:16:45.433 回答
10

首先,() => X=> X无参数方法完全无关。

现在,写这样的东西看起来很傻:

var x() = 5
val y() = 2
x() = x() + y()

现在,如果您不遵循上述与无参数方法的关系,那么您应该查找统一访问原则。以上都是方法声明,都可以用def. 也就是说,假设您删除了它们的括号。

于 2012-09-09T00:46:26.177 回答
8

除了提到的约定事实(副作用与非副作用)之外,它还有助于解决以下几种情况:

有空括号的用处

// short apply syntax

object A {
  def apply() = 33
}

object B {
  def apply   = 33
}

A()   // works
B()   // does not work

// using in place of a curried function

object C {
  def m()() = ()
}

val f: () => () => Unit = C.m

没有父母的用处

// val <=> def, var <=> two related defs

trait T { def a:   Int; def a_=(v: Int): Unit }
trait U { def a(): Int; def a_=(v: Int): Unit }

def tt(t: T): Unit = t.a += 1  // works
def tu(u: U): Unit = u.a += 1  // does not work

// avoiding clutter with apply the other way round

object D {
  def a   = Vector(1, 2, 3)
  def b() = Vector(1, 2, 3)
}

D.a(0)  // works
D.b(0)  // does not work

// object can stand for no-paren method

trait E
trait F { def f:   E }
trait G { def f(): E }

object H extends F {
  object f extends E  // works
}

object I extends G {
  object f extends E  // does not work
}

因此,就语言的规律性而言,区分是有意义的(特别是对于最后显示的情况)。

于 2012-09-09T09:29:51.633 回答
4

我想说两者都是可能的,因为您可以使用无参数方法访问可变状态:

class X(private var x: Int) {
  def inc() { x += 1 }
  def value = x
}

该方法value没有副作用(它只访问可变状态)。Scala 编程中明确提到了这种行为:

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

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

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

于 2012-09-08T22:45:36.297 回答