2008-09-23 15 views
8

Uso de línea de comandos de Scala REPL:semántica recursivas sobrecarga en el Scala REPL - idiomas JVM

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

da

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

Parece que no se puede definir métodos sobrecargados recursivas de la REPL. Pensé que esto era un error en Scala REPL y lo archivé, pero se cerró casi instantáneamente con "wontfix: no veo ninguna manera de que esto pueda ser respaldado dada la semántica del intérprete, porque estos dos métodos deben compilarse". juntos." Él recomendó poner los métodos en un objeto cerrado.

¿Hay una implementación de lenguaje JVM o un experto en Scala que pueda explicar por qué? Puedo ver que sería un problema si los métodos se llamaran entre sí, por ejemplo, ¿pero en este caso?

O si esta es una pregunta demasiado grande y cree que necesito más conocimientos previos, ¿alguien tiene buenos enlaces a libros o sitios sobre implementaciones de idiomas, especialmente en la JVM? (Conozco el blog de John Rose, y el libro Programming Language Pragmatics ... pero eso es todo :)

Respuesta

11

El problema se debe a que el intérprete más a menudo tiene que reemplazar elementos existentes con un nombre dado, en lugar de sobrecargarlos. Por ejemplo, a menudo va a correr a través de la experimentación con algo, a menudo creando un método llamado test:

def test(x: Int) = x + x 

Un poco más tarde, digamos que estoy corriendo un experimento diferente y crear otro método llamado test, sin relación con el primero:

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

Esto no es un escenario completamente irreal. De hecho, es precisamente cómo la mayoría de las personas usa el intérprete, a menudo sin siquiera darse cuenta. Si el intérprete decidió arbitrariamente mantener ambas versiones de test en el alcance, eso podría conducir a confusas diferencias semánticas en el uso de la prueba. Por ejemplo, podríamos hacer una llamada a test, accidentalmente pasar una Int en lugar de List[Int] (no el accidente más probable en el mundo):

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

Con el tiempo, el alcance de la raíz del intérprete obtendría increíblemente lleno de varias versiones de métodos, campos, etc. Tiendo a dejar mi intérprete abierto durante días, pero si se permitiera una sobrecarga como esta, nos veríamos obligados a "limpiar" al intérprete cada cierto tiempo, ya que las cosas serían demasiado confusas. .

No es una limitación del compilador JVM o Scala, es una decisión de diseño deliberada. Como se menciona en el error, aún puede sobrecargarse si se encuentra dentro de algo que no sea el alcance raíz. Encerrar tus métodos de prueba dentro de una clase parece ser la mejor solución para mí.

+0

Excelente respuesta Daniel, gracias. Además, me gusta tu blog. :) –

4

REPL aceptará si copia ambas líneas y las pega al mismo tiempo.

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

Como se muestra en extempore's respuesta, es posible sobrecargar. Daniel's comentario sobre la decisión de diseño es correcto, pero, creo, incompleto y un poco engañoso. No hay que proscribe de sobrecargas (ya que son posibles), pero no se logran fácilmente.

Las decisiones de diseño que llevan a esta son:

  1. Todas las definiciones anteriores deben estar disponibles.
  2. Solo se compila el código recientemente ingresado, en lugar de recompilar todo lo que se haya ingresado cada vez.
  3. Debe ser posible redefinir las definiciones (como mencionó Daniel).
  4. Debe ser posible definir miembros como vals y defs, no solo clases y objetos.

El problema es ... ¿cómo lograr todos estos objetivos? ¿Cómo procesamos tu ejemplo?

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

A partir de la cuarta artículo, o un valdef sólo puede ser definido dentro de un class, trait, object o package object. Así, REPL pone las definiciones dentro de los objetos, como este (representación no real!)

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

Ahora, debido a cómo funciona la JVM, una vez que ha definido uno de ellos, no se puede extenderlas. Podrías, por supuesto, recompilar todo, pero descartamos eso. Así que hay que colocarlo en un lugar diferente:

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

y esto explica por qué los ejemplos no son sobrecargas: se definen en dos lugares diferentes. Si los coloca en la misma línea, todos se definirían juntos, lo que los convertiría en sobrecargas, como se muestra en el ejemplo de imprompore.

En cuanto a las otras decisiones de diseño, cada paquete nuevo importa definiciones y "res" de paquetes anteriores, y las importaciones pueden oscurecerse entre sí, lo que permite "redefinir" cosas.

Cuestiones relacionadas