scala recursion jvm jvm-languages

Semántica de sobrecarga recursiva en Scala REPL-JVM languages



recursion jvm-languages (4)

Usando la línea de comando de Scala REPL:

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 puede definir métodos recursivos sobrecargados en 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 crees que necesito más conocimientos previos, ¿alguien tiene buenos enlaces a libros o sitios sobre implementaciones de idiomas, especialmente en JVM? (Sé sobre el blog de John Rose, y el libro Programming Language Pragmatics ... pero eso es todo. :)


El problema se debe al hecho de que el intérprete más a menudo tiene que reemplazar los elementos existentes con un nombre dado, en lugar de sobrecargarlos. Por ejemplo, a menudo estoy experimentando con algo, a menudo creando un método llamado test :

def test(x: Int) = x + x

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

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

Este 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 la 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 para test , pasando accidentalmente un Int lugar de List[Int] (no el accidente más improbable del mundo):

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

Con el tiempo, el alcance de la raíz del intérprete se volvería increíblemente desordenado con 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 " enjuague "el intérprete de vez en cuando ya que las cosas se vuelven demasiado confusas.

No es una limitación de la JVM o del compilador de 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í.


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


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


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

Las decisiones de diseño que conducen a esto son:

  1. Todas las definiciones previas deben estar disponibles.
  2. Solo se compila el código recién ingresado, en lugar de recompilar todo lo que se haya ingresado en cada ocasión.
  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))}

Comenzando con el 4º elemento, A val o def solo pueden definirse dentro de una class , trait , object u package object . Entonces, REPL pone las definiciones dentro de los objetos, así (¡ no representación 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 JVM, una vez que haya definido uno de ellos, no podrá extenderlos. Podrías, por supuesto, recompilar todo, pero descartamos eso. Entonces debes 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é tus ejemplos no son sobrecargas: están definidos 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 nuevo paquete importa definiciones y "res" de paquetes anteriores, y las importaciones pueden oscurecerse entre sí, lo que permite "redefinir" cosas.