2008-09-23 76 views
8

使用Scala的命令行REPL:递归超载语义 - JVM语言

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

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

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

是否有JVM语言实现或Scala专家谁可以解释为什么?我可以看到,如果方法互相调用,但在这种情况下会出现问题?

或者,如果这个问题太大了,而且您认为我需要更多的必备知识,那么某人是否有任何有关语言实现的书籍或网站的良好链接,特别是在JVM上? (我知道约翰·罗斯的博客和编程语言语用学的书......但是就是这样。:)

回答

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编译器的限制,而是一个深思熟虑的设计决定。正如错误中提到的,如果你在根作用域之外,你仍然可以重载。在班级中包含您的测试方法似乎是对我来说最好的解决方案。

+0

优秀的答案Daniel,谢谢。另外,我喜欢你的博客。 :) – 2008-09-23 18:27:29

4

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

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") 
() 
1

extempore's所示,答案可能过载。 Daniel's关于设计决策的评论是正确的,但是,我认为,不完整并且有点误导。没有超载过载(因为它们是可能的),但它们不容易实现。

的设计决策导致这种是:

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

问题是......如何实现所有这些目标?我们如何处理你的例子?

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

与第四项开始,A valdef只能一个classtrait,或object内部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”,并且导入可以相互影响,这使得可以“重新定义”东西。