【问题标题】:Confusing resolution of a named argument命名参数的混淆解析
【发布时间】:2015-10-12 09:28:59
【问题描述】:

以下代码给我一个 Scala 2.11.7 的错误,这让我很困惑:

class A(val a: String, val bVal: Option[String] = None) {
  val b: String = bVal.getOrElse("")

  def copy(a: String = a, b: Option[String] = Some(b)) = new A(a, b)
}

IntelliJ IDE 没有显示任何错误,但在编译时出现错误:

Error:(4, 52) type mismatch;
 found   : Option[String]
 required: String
  def copy(a: String = a, b: Option[String] = Some(b)) = new A(a, b)
                                                   ^

为了比较,这编译得很好:

class A(val a: String, val bVal: Option[String] = None) {
  val b = bVal
  def copy(a: String = a, b: Option[String] = Some(b.getOrElse(""))) = new A(a, b)
}

当我用Some(this.b) 替换Some(b) 时,错误消失了,但我仍然很困惑为什么首先出现错误。看起来编译器正在将Some 中的b 解析为copy 的参数,而不是Ab 成员。如果是这样,第二个版本怎么编译不出错?

【问题讨论】:

  • 如果这不仅仅是展示这种有趣行为的示例,那么当您仅使用 None 大小写来定义默认值 ("" ) 为b。将A 定义为class A(val a: String, val b: String = "") { ... } 会更容易。
  • @PeterNeyens 这只是一个从更复杂的真实代码简化的展示。

标签: scala named-parameters


【解决方案1】:

这一定是一个错误。默认表达式的范围包括以前的参数列表。

scala> def x = 1
x: Int

scala> def f(x: Int = x) = 2 * x
f: (x: Int)Int

scala> f()
res0: Int = 2

-Xprint:typer显示默认是正确的:

    class A extends scala.AnyRef {
      <paramaccessor> private[this] val a: String = _;
      <stable> <accessor> <paramaccessor> def a: String = A.this.a;
      <paramaccessor> private[this] val bVal: Option[String] = _;
      <stable> <accessor> <paramaccessor> def bVal: Option[String] = A.this.bVal;
      def <init>(a: String, bVal: Option[String] = scala.None): A = {
        A.super.<init>();
        ()
      };
      private[this] val b: String = A.this.bVal.getOrElse[String]("");
      <stable> <accessor> def b: String = A.this.b;
      def copy(a: String = a, b: Option[String] = scala.Some.apply[A](<b: error>)): A = new $iw.this.A(a, b);
      <synthetic> def copy$default$1: String = A.this.a;
      <synthetic> def copy$default$2: Option[String] = scala.Some.apply[String](A.this.b)
    };
    <synthetic> object A extends AnyRef {
      def <init>(): A.type = {
        A.super.<init>();
        ()
      };
      <synthetic> def <init>$default$2: Option[String] = scala.None
    }

是的。

scala> def y = "hi"
y: String

scala> def g(x: String)(y: Option[String] = Some(x)) = y map (_ * 2)
g: (x: String)(y: Option[String])Option[String]

scala> g("bye")()
res0: Option[String] = Some(byebye)

scala> def g(x: String)(y: Option[String] = Some(y)) = y map (_ * 2)
<console>:11: error: type mismatch;
 found   : Option[String]
 required: String
       def g(x: String)(y: Option[String] = Some(y)) = y map (_ * 2)
                                                 ^

编辑:默认方法正确这一事实并不意味着什么,因为存在与默认方法不是很卫生的事实相关的谜题。例如,http://scalapuzzlers.com/#pzzlr-051

【讨论】:

【解决方案2】:

您的理解是正确的,当存在与周围类型成员同名的参数时,编译器将首选该参数。

在您的非编译版本中,b 的类型为 Option[String]。这意味着Some(b) 的类型为Option[Option[String]],它与A 的构造函数的第二个参数的类型不匹配。

在第二个编译版本中,b 仍然是Option[String] 类型。但是,b.getOrElse("") 的类型为 String。这意味着Some(b.getOrElse("")) 具有Option[String] 类型,这对于A 的第二个构造函数参数有效。

【讨论】:

  • 然后让我吃惊的是a 的运行时行为,它似乎是按预期从a 成员初始化的,而不是从a 参数初始化,这将是循环初始化,我会期望null 或类似的东西,而不是a 成员的值。对此有何解释?
  • 您将a 参数默认为a 成员。所以copy() 将使用a 成员和copy(b = Some("str"))。但是copy(a = "str") 将使用a 参数,而不是a 成员。
  • 我也很惊讶该参数在参数列表中完全可见。 Named and Default Arguments in Scala 2.8 声明“参数的范围扩展到所有后续参数列表...默认表达式可以依赖...(但不能依赖于同一参数列表中的其他参数)。”
  • 默认ab有什么区别?为什么编译器会将a 成员作为a 参数,而不是b 一个?是因为b 是复杂表达式的一部分吗?
  • 关于使用命名参数区分应用程序的持续讨论,f(x = x) 是什么意思,其中诀窍是f { x = x } 将被赋值。它只是表明所有这些语法结果是多么棘手。不就是范围吗?
猜你喜欢
  • 1970-01-01
  • 2013-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-06
相关资源
最近更新 更多