Recursive overloading semantics in the Scala REPL - JVM languages

孤人 提交于 2019-12-10 12:53:38

问题


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. :)


回答1:


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.




回答2:


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



回答3:


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




回答4:


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.



来源:https://stackoverflow.com/questions/120702/recursive-overloading-semantics-in-the-scala-repl-jvm-languages

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!