Recursive overloading semantics in the Scala REPL - JVM languages
Asked Answered
B

4

8

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

Bumblebee answered 23/9, 2008 at 12:35 Comment(0)
M
11

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.

Medford answered 23/9, 2008 at 17:6 Comment(0)
A
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")
()
Anticathode answered 8/9, 2010 at 4:3 Comment(0)
T
4

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

Tallis answered 23/9, 2008 at 15:15 Comment(0)
E
1

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.

Eady answered 30/6, 2012 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.