8

使用 Scala 的命令行 REPL:

def foo(x: Int): Unit = {}
def foo(x: String): Unit = {println(foo(2))}

error: type mismatch;
found: Int(2)
required: String

看来你不能在 REPL 中定义重载的递归方法。我认为这是 Scala REPL 中的一个错误并提交了它,但它几乎立即以“wontfix 关闭:鉴于解释器的语义,我看不出有任何方法可以支持,因为必须编译这两种方法一起。” 他建议将这些方法放在一个封闭的对象中。

是否有 JVM 语言实现或 Scala 专家可以解释原因?例如,如果这些方法相互调用,我可以看到这将是一个问题,但在这种情况下呢?

或者,如果这是一个太大的问题,而您认为我需要更多的先决知识,那么是否有人有任何关于语言实现的书籍或网站的良好链接,尤其是在 JVM 上?(我知道 John Rose 的博客,以及 Programming Language Pragmatics 这本书……但仅此而已。:)

4

4 回答 4

11

问题是由于解释器通常必须用给定名称替换现有元素,而不是重载它们。例如,我经常会尝试一些东西,经常创建一个名为test

def test(x: Int) = x + x

稍后,假设我正在运行一个不同的实验,我创建了另一个名为 的方法test,与第一个方法无关:

def test(ls: List[Int]) = (0 /: ls) { _ + _ }

这不是一个完全不现实的场景。事实上,这正是大多数人使用解释器的方式,通常甚至没有意识到。如果解释器任意决定将两个版本都保留test在范围内,则可能会导致在使用测试时混淆语义差异。例如,我们可能会调用test,不小心传递了一个Int而不是List[Int](这不是世界上最不可能发生的事故):

test(1 :: Nil)  // => 1
test(2)         // => 4  (expecting 2)

随着时间的推移,解释器的根范围会变得非常混乱,其中包含各种版本的方法、字段等。我倾向于让我的解释器一次打开几天,但如果允许这样的重载,我们将被迫“经常冲洗”解释器,因为事情变得太混乱了。

这不是 JVM 或 Scala 编译器的限制,而是经过深思熟虑的设计决定。如错误中所述,如果您在根范围以外的范围内,您仍然可以重载。在一个类中包含你的测试方法对我来说似乎是最好的解决方案。

于 2008-09-23T17:06:52.137 回答
5
% scala28
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def foo(x: Int): Unit = () ; def foo(x: String): Unit = { println(foo(2)) } 
foo: (x: String)Unit <and> (x: Int)Unit
foo: (x: String)Unit <and> (x: Int)Unit

scala> foo(5)

scala> foo("abc")
()
于 2010-09-08T04:03:22.987 回答
4

如果您复制两行并同时粘贴两行,REPL 将接受。

于 2008-09-23T15:15:29.673 回答
1

extempore's answer所示,有可能超载。丹尼尔关于设计决策的评论是正确的,但是,我认为,不完整且有点误导。没有禁止重载(因为它们是可能的),但它们不容易实现。

导致这种情况的设计决策是:

  1. 所有以前的定义都必须可用。
  2. 只编译新输入的代码,而不是每次都重新编译曾经输入的所有代码。
  3. 必须可以重新定义定义(如丹尼尔所述)。
  4. 必须可以定义成员,例如 vals 和 defs,而不仅仅是类和对象。

问题是……如何实现所有这些目标?我们如何处理您的示例?

def foo(x: Int): Unit = {}
def foo(x: String): Unit = {println(foo(2))}

从第 4 项开始,A valordef只能在 a class, trait, objector中定义package object。因此,REPL 将定义放在对象中,就像这样(不是实际表示!

package $line1 { // input line
  object $read { // what was read
    object $iw { // definitions
      def foo(x: Int): Unit = {}
    }
    // val res1 would be here somewhere if this was an expression
  }
}

现在,由于 JVM 的工作方式,一旦定义了其中之一,就无法扩展它们。当然,您可以重新编译所有内容,但我们放弃了它。所以你需要把它放在不同的地方:

package $line1 { // input line
  object $read { // what was read
    object $iw { // definitions
      def foo(x: String): Unit = { println(foo(2)) }
    }
  }
}

这解释了为什么您的示例不是重载:它们是在两个不同的地方定义的。如果将它们放在同一行,它们将被一起定义,这会使它们重载,如 extempore 的示例所示。

至于其他设计决策,每个新包导入定义和以前包的“res”,并且导入可以相互影响,这使得“重新定义”东西成为可能。

于 2012-06-30T19:17:07.280 回答