sera - videos de musica romantica rudy la scala
Nulos en Scala... ¿por qué es esto posible? (3)
¿Por qué exactamente el compilador Scala no explota con errores?
Porque este problema no se puede resolver en el caso general. ¿Conoces el problema de la detención ? El problema de la detención dice que no es posible escribir un algoritmo que descubra si un programa se detiene alguna vez. Dado que el problema de averiguar si una definición recursiva daría lugar a una asignación nula se puede reducir al problema de detención, tampoco es posible resolverlo.
Bueno, ahora es bastante fácil prohibir las definiciones recursivas, esto se hace, por ejemplo, para los valores que no son valores de clase:
scala> def f = { val k: String = k+"abc" }
<console>:11: error: forward reference extends over definition of value k
def f = { val k: String = k+"abc" }
^
Para los valores de clase, esta característica no está prohibida por varias razones:
- Su alcance no es limitado.
- La JVM los inicializa con un valor predeterminado (que es nulo para los tipos de referencia).
- Los valores recursivos son útiles.
Su caso de uso es trivial, como esto:
scala> val k: String = k+"abc"
k: String = nullabc
Pero ¿qué pasa con esto?
scala> object X { val x: Int = Y.y+1 }; object Y { val y: Int = X.x+1 }
defined object X
defined object Y
scala> X.x
res2: Int = 2
scala> Y.y
res3: Int = 1
scala> object X { val x: Int = Y.y+1 }; object Y { val y: Int = X.x+1 }
defined object X
defined object Y
scala> Y.y
res4: Int = 2
scala> X.x
res5: Int = 1
O esto:
scala> val f: Stream[BigInt] = 1 #:: 1 #:: f.zip(f.tail).map { case (a,b) => a+b }
f: Stream[BigInt] = Stream(1, ?)
scala> f.take(10).toList
res7: List[BigInt] = List(1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
Como puede ver, es bastante fácil escribir programas donde ya no es obvio a qué valor darán resultado. Y como el problema de la detención no es solucionable, no podemos dejar que el compilador haga el trabajo por nosotros en casos no triviales.
Esto también significa que los casos triviales, como el que se muestra en su pregunta, podrían estar codificados en el compilador. Pero como no puede existir un algoritmo que pueda detectar todos los posibles casos triviales, todos los casos que se encuentren deben estar codificados en el compilador (sin mencionar que no existe una definición de un caso trivial). Por lo tanto, no sería prudente comenzar a codificar algunos de estos casos. En última instancia, resultaría en un compilador más lento y un compilador que es más difícil de mantener.
Se podría argumentar que para un caso de uso que quema a cada segundo usuario, sería prudente al menos codificar un escenario tan extremo. Por otro lado, algunas personas solo necesitan ser quemadas para aprender algo nuevo. ;)
Estaba codificando en Scala y haciendo una rápida refactorización en Intellij, cuando me topé con la siguiente pieza de rareza ...
package misc
/**
* Created by abimbola on 05/10/15.
*/
object WTF extends App {
val name: String = name
println(s"Value is: $name")
}
Entonces noté que el compilador no se quejaba, así que decidí intentar ejecutar esto y obtuve una salida muy interesante.
Value is: null
Process finished with exit code 0
¿Alguien puede decirme por qué esto funciona?
EDITAR:
Primer problema, al nombre del valor se le asigna una referencia a sí mismo aunque aún no exista; ¿Por qué exactamente el compilador de Scala no explota con errores?
¿Por qué el valor de la asignación es nulo?
1.) ¿Por qué el compilador no explota?
Aquí hay un ejemplo reducido. Esto se compila porque a través del tipo dado se puede inferir un valor predeterminado:
class Example { val x: Int = x }
scalac Example.scala
Example.scala:1: warning: value x in class Example does nothing other than call itself recursively
class Example { val x: Int = x }
Esto no se compila porque no se puede inferir un valor predeterminado:
class ExampleDoesNotCompile { def x = x }
scalac ExampleDoesNotCompile.scala
ExampleDoesNotCompile.scala:1: error: recursive method x needs result type
class ExampleDoesNotCompile { def x = x }
1.1 que pasa aqui
Mi interpretación. Así que tenga cuidado: el principio de acceso uniforme entra en acción. La asignación al val x
llama al elemento de acceso x()
que devuelve el valor unitario de x. Así que x se establece en el valor predeterminado.
class Example { val x: Int = x }
^
[[syntax trees at end of cleanup]] // Example.scala
package <empty> {
class Example extends Object {
private[this] val x: Int = _;
<stable> <accessor> def x(): Int = Example.this.x;
def <init>(): Example = {
Example.super.<init>();
Example.this.x = Example.this.x();
()
}
}
} ^
2.) Por qué el valor es nulo
Los valores predeterminados están determinados por el entorno en el que se compila Scala.
En el ejemplo que has dado, parece que corres en la JVM. El valor predeterminado para Objeto aquí es null
.
Por lo tanto, cuando no proporciona un valor, el valor predeterminado se utiliza como reserva.
Valores por defecto JVM:
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char ''/u0000''
boolean false
Object null // String are objects.
También el valor predeterminado es un valor válido para el tipo dado: Aquí hay un ejemplo en el REPL:
scala> val x : Int = 0
x: Int = 0
scala> val x : Int = null
<console>:10: error: an expression of type Null is ineligible for implicit conversion
val x : Int = null
^
scala> val x : String = null
x: String = null
Creo que la answer @Andreas ya tiene la información necesaria. Intentaré dar una explicación adicional:
Cuando escribes val name: String = name
en el nivel de clase, esto hace varias cosas diferentes al mismo tiempo:
- crear el
name
campo - crear el
name()
del captadorname()
- crear código para la asignación
name = name
, que se convierte en parte del constructor principal
Esto es lo que se hace explícito por Andreas 1.1.
package <empty> {
class Example extends Object {
private[this] val x: Int = _;
<stable> <accessor> def x(): Int = Example.this.x;
def <init>(): Example = {
Example.super.<init>();
Example.this.x = Example.this.x();
()
}
}
}
La sintaxis no es Scala, es (como lo sugiere [[syntax trees at end of cleanup]]
) una representación textual de lo que el compilador convertirá posteriormente en código de bytes. Aparte de la sintaxis desconocida, podemos interpretar esto, como lo haría la JVM:
- La JVM crea un objeto. En este punto, todos los campos tienen valores por defecto.
val x: Int = _;
es comoint x;
en Java, es decir, se utiliza el valor predeterminado de JVM, que es0
paraI
(es decir,int
en Java oInt
en Scala) - El constructor se llama para el objeto.
- (Se llama el super constructor)
- el constructor llama
x()
-
x()
devuelvex
, que es0
-
x
se asigna a0
- el constructor regresa
Como puede ver, después del paso de análisis inicial, no hay nada en el árbol de sintaxis que parezca inmediatamente incorrecto, a pesar de que el código fuente original se ve mal. No diría que este es el comportamiento que espero, así que me imagino una de tres cosas:
- O bien, los desarrolladores de Scala lo vieron como demasiado complejo para reconocer y prohibir
- O bien, es una regresión y simplemente no se encontró como un error
- o es una "característica" y existe una necesidad legítima de este comportamiento
(El pedido refleja mi opinión de simpatía, en orden decreciente)