为什么 SAM 规则不适用于无参数方法

Why SAM rule doesn't work on parameterless method

// ok
val sam0: MySamWithEmptyParameter = () => 100

// doesn't work
//  val sam1: MySamWithParameterless = () => 100

trait MySamWithEmptyParameter {
  def receive(): Int
}

trait MySamWithParameterless {
  def receive: Int
}

为什么 sam1 无法覆盖 receive 方法? scalac 将两个特征编译为相同的代码。

abstract trait TestSAM$MySamWithEmptyParameter extends Object {
  def receive(): Int
};

abstract trait TestSAM$MySamWithParameterless extends Object {
  def receive(): Int
};

SI-10555 正是在谈论这个。这是一个简单的设计决定,只支持一个显式的空参数列表,即使两者编译成一个空参数列表也是如此。

The relevant part of the Specification says(强调我的):

  • the method m must have a single argument list;

这确实有点尴尬,因为 eta 扩展确实适用于参数列表为空的方法。

编辑

联系了 Lightbend 的人。以下是 Scala 团队负责人 Adrian Moors 的回复:

The original reason was to keep the spec simple, but perhaps we should revisit. I agree it’s surprising that it works for def a(): Int, but not in your example.

Internally, methods that don’t define an argument list at all, and those that do (even if empty) are treated differently. This has led to confusion/bugs before — to name just one: https://github.com/scala/scala-dev/issues/284.

In 2.13, we’re reworking eta-expansion (it will apply more aggressively, but ()-insertion will happen first). We’ve been back and forth on this, but the current thinking is:

  • 0-ary methods are treated specially: if the expected type is sam-equivalent to Function0, we eta-expand; otherwise, () is inserted (in dotty, you are required to write the () explicitly, unless the method is java-defined) — I’m still not sure about whether we should ever eta-expand here
  • for all other arities, a method reference is eta-expanded regardless of the expected type (if there’s no type mismatch, this could hide errors when you refactor a method to take arguments, but forget to apply them everywhere. However, since functions are first-class values, it should be easy to construct them by simplify referring to a method value).

The upshot is that we can deprecate method value syntax (m _), since it’s subsumed by simply writing m. (Note that this is distinct from placeholder syntax, as in m(, _).) (See also the thread around this comment: https://github.com/lampepfl/dotty/issues/2570#issuecomment-306202339)