Scala REPL 中的递归重载语义 - JVM 语言

发布于 2024-07-05 03:03:47 字数 556 浏览 12 评论 0原文

使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

煮茶煮酒煮时光 2024-07-12 03:03:47

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

REPL will accept if you copy both lines and paste both at same time.

魄砕の薆 2024-07-12 03:03:47

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

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

  1. 所有先前的定义都必须可用。
  2. 仅编译新输入的代码,而不是每次都重新编译输入的所有内容。
  3. 必须有可能重新定义定义(正如丹尼尔提到的)。
  4. 必须能够定义诸如 val 和 def 之类的成员,而不仅仅是类和对象。

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

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

从第四项开始, valdef 只能在 classtrait内部定义>对象包对象。 因此,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)) }
    }
  }
}

这解释了为什么您的示例不是重载:它们是在两个不同的位置定义的。 如果将它们放在同一行中,它们将全部定义在一起,这将使它们重载,如即席示例所示。

至于其他设计决策,每个新包都会导入以前包的定义和“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:

  1. All previous definitions must be available.
  2. Only newly entered code is compiled, instead of recompiling everything ever entered every time.
  3. It must be possible to redefine definitions (as Daniel mentioned).
  4. It must be possible to define members such as vals and defs, not only classes and objects.

The problem is... how to achieve all these goals? How do we process your example?

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

Starting with the 4th item, A val or def can only be defined inside a class, trait, object or package object. So, REPL puts the definitions inside objects, like this (not actual representation!)

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
  }
}

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:

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

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.

静赏你的温柔 2024-07-12 03:03:47
% 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")
()
% 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")
()
潦草背影 2024-07-12 03:03:47

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

def test(x: Int) = x + x

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

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

这并不是一个完全不切实际的场景。 事实上,这正是大多数人使用口译员的方式,通常甚至没有意识到。 如果解释器任意决定将两个版本的 test 保留在范围内,则可能会导致使用 test 时出现令人困惑的语义差异。 例如,我们可能会调用 test,意外地传递了 Int 而不是 List[Int](这不是最不可能发生的事故) world):

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

随着时间的推移,解释器的根作用域会变得非常混乱,充满了各种版本的方法、字段等。我倾向于让我的解释器一次打开几天,但如果允许这样的重载,我们就会由于事情变得太混乱,被迫经常“冲洗”口译员。

这不是 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:

def test(x: Int) = x + x

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:

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

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 to test, accidentally passing an Int rather than List[Int] (not the most unlikely accident in the world):

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文