Scala REPL 中的递归重载语义 - JVM 语言
使用 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 的博客和《编程语言语用学》一书……仅此而已。:)
Using Scala's command line REPL:
def foo(x: Int): Unit = {}
def foo(x: String): Unit = {println(foo(2))}
gives
error: type mismatch;
found: Int(2)
required: String
It seems that you can't define overloaded recursive methods in the REPL. I thought this was a bug in the Scala REPL and filed it, but it was almost instantly closed with "wontfix: I don't see any way this could be supported given the semantics of the interpreter, because these two methods must to be compiled together." He recommended putting the methods in an enclosing object.
Is there a JVM language implementation or Scala expert who could explain why? I can see it would be a problem if the methods called each other for instance, but in this case?
Or if this is too large a question and you think I need more prerequisite knowledge, does someone have any good links to books or sites about language implementations, especially on the JVM? (I know about John Rose's blog, and the book Programming Language Pragmatics... but that's about it. :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您复制这两行并同时粘贴,REPL 将接受。
REPL will accept if you copy both lines and paste both at same time.
正如extempore的答案所示,有可能过载。 Daniel 关于设计决策的评论是正确的,但我认为不完整且有点误导。 重载并不取缔(因为它们是可能的),但它们并不容易实现。
导致这种情况的设计决策是:
问题是……如何实现所有这些目标? 我们如何处理您的示例?
从第四项开始,
val
或def
只能在class
、trait
、内部定义>对象
或包对象
。 因此,REPL 将定义放在对象内部,如下所示(不是实际表示!)。现在,由于 JVM 的工作方式,一旦定义了其中一个对象,就无法扩展它们。 当然,您可以重新编译所有内容,但我们放弃了它。 因此,您需要将其放在不同的位置:
这解释了为什么您的示例不是重载:它们是在两个不同的位置定义的。 如果将它们放在同一行中,它们将全部定义在一起,这将使它们重载,如即席示例所示。
至于其他设计决策,每个新包都会导入以前包的定义和“res”,并且导入可以相互影响,这使得“重新定义”东西成为可能。
As shown by extempore's answer, it is possible to overload. Daniel's comment about design decision is correct, but, I think, incomplete and a bit misleading. There's no outlawing of overloads (since they are possible), but they are not easily achieved.
The design decisions that lead to this are:
The problem is... how to achieve all these goals? How do we process your example?
Starting with the 4th item, A
val
ordef
can only be defined inside aclass
,trait
,object
orpackage object
. So, REPL puts the definitions inside objects, like this (not actual representation!)Now, due to how JVM works, once you defined one of them, you can't extend them. You could, of course, recompile everything, but we discarded that. So you need to place it in a different place:
And this explains why your examples are not overloads: they are defined in two different places. If you put them in the same line, they'd all be defined together, which would make them overloads, as shown in extempore's example.
As for the other design decisions, each new package import definitions and "res" from previous packages, and the imports can shadow each other, which makes it possible to "redefine" stuff.
该问题是由于解释器通常必须用给定名称替换现有元素,而不是重载它们。 例如,我经常会尝试一些东西,通常会创建一个名为
test
的方法:稍后,假设我正在运行一个不同的实验,并且我创建了另一个名为
test
的方法,与第一个方法无关:这并不是一个完全不切实际的场景。 事实上,这正是大多数人使用口译员的方式,通常甚至没有意识到。 如果解释器任意决定将两个版本的
test
保留在范围内,则可能会导致使用 test 时出现令人困惑的语义差异。 例如,我们可能会调用test
,意外地传递了Int
而不是List[Int]
(这不是最不可能发生的事故) world):随着时间的推移,解释器的根作用域会变得非常混乱,充满了各种版本的方法、字段等。我倾向于让我的解释器一次打开几天,但如果允许这样的重载,我们就会由于事情变得太混乱,被迫经常“冲洗”口译员。
这不是 JVM 或 Scala 编译器的限制,而是经过深思熟虑的设计决策。 正如错误中提到的,如果您位于根范围之外的其他范围内,您仍然可以重载。 对我来说,将测试方法封装在一个类中似乎是最好的解决方案。
The issue is due to the fact that the interpreter most often has to replace existing elements with a given name, rather than overload them. For example, I will often be running through experimenting with something, often creating a method called
test
:A little later on, let's say that I'm running a different experiment and I create another method named
test
, unrelated to the first:This isn't an entirely unrealistic scenario. In fact, it's precisely how most people use the interpreter, often without even realizing it. If the interpreter arbitrarily decided to keep both versions of
test
in scope, that could lead to confusing semantic differences in using test. For example, we might make a call totest
, accidentally passing anInt
rather thanList[Int]
(not the most unlikely accident in the world):Over time, the root scope of the interpreter would get incredibly cluttered with various versions of methods, fields, etc. I tend to leave my interpreter open for days at a time, but if overloading like this were allowed, we would be forced to "flush" the interpreter every so often as things got to be too confusing.
It's not a limitation of the JVM or the Scala compiler, it's a deliberate design decision. As mentioned in the bug, you can still overload if you're within something other than the root scope. Enclosing your test methods within a class seems like the best solution to me.