yuri videos sera rudy rudilascala rubí romantica que prohibidos por musical musica letra fotos escala amores scala intellij-idea

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:

  1. 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?

  2. ¿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 captador name()
  • 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 como int x; en Java, es decir, se utiliza el valor predeterminado de JVM, que es 0 para I (es decir, int en Java o Int en Scala)
  • El constructor se llama para el objeto.
  • (Se llama el super constructor)
  • el constructor llama x()
  • x() devuelve x , que es 0
  • x se asigna a 0
  • 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)