recorrer listas lista introduccion funciones ejemplos comentarios clases scala functional-programming static-typing

listas - recorrer lista en scala



¿Qué reglas especiales tiene el compilador de scala para el tipo de unidad dentro del sistema de tipos? (3)

Como está escrito en el capítulo 6.26.1 de la especificación del lenguaje scala :

Valor de descarte

Si e tiene algún tipo de valor y el tipo esperado es Unidad, e se convierte al tipo esperado incrustándolo en el término {e; ()}.

La Unit recibe un manejo especial por parte del compilador cuando genera un código de bytes porque es análogo a void en el jvm. Pero conceptualmente como un tipo dentro del sistema de tipo scala, parece que también recibe un tratamiento especial en el lenguaje en sí (ejemplos a continuación).

Entonces, mi pregunta es acerca de aclarar esto y entender qué mecanismos se utilizan y si realmente hay un tratamiento especial para el tipo de Unit .

Ejemplo 1:

Para tipos de scala "normales" como Seq , si un método devuelve Seq , entonces debe devolver Seq (o un tipo más específico que extienda Seq )

def foo1: Seq[Int] = List(1, 2, 3) def foo2: Seq[Int] = Vector(1, 2, 3) def foo3: Seq[Int] = "foo" // Fails

Los dos primeros ejemplos se compilan porque List[Int] y Vector[Int] son subtipos de Seq[Int] . El tercero falla porque String no lo es.

Pero si cambio el tercer ejemplo para devolver la Unit , se compilará y ejecutará sin problemas, aunque String no sea un subtipo de Unit :

def foo3(): Unit = "foo" // Compiles (with a warning)

No conozco ningún otro tipo para el que se permita esta excepción en Scala. Entonces, ¿el compilador tiene reglas especiales para el tipo de Unit en el nivel de sistema de tipo, o existe algún tipo de mecanismo más general en funcionamiento, por ejemplo, una conversión implícita?

Ejemplo 2:

Tampoco tengo claro cómo interactúa la unidad en situaciones en las que normalmente se aplicarían las reglas de variación.

Por ejemplo, a veces nos encontramos con este error en Future[Unit] donde accidentalmente usamos map lugar de flatMap y creamos un Future[Future] :

def save(customer: Customer): Future[Unit] = ... // Save to database def foo: Future[Unit] = save(customer1).map(_ => save(customer2))

El map está creando un Future[Future[Unit]] y el compilador requiere un Future[Unit] . Sin embargo, esto compila!

Al principio pensé que esto era porque Future[+T] es covariante, pero en realidad Future[Unit] no es un subtipo de Unit por lo que no parece ser eso.

Si el tipo se cambia a Boolean por ejemplo, el compilador detecta el error:

def save(customer: Customer): Future[Boolean] = ... def foo: Future[Boolean] = save(customer1).map(_ => save(customer2)) // Compiler fails this

Y para cualquier otro tipo que no sea de Unit , no se compilará (excepto Any porque Future[Any] es un subtipo de Any por coincidencia).

Entonces, ¿el compilador tiene reglas especiales en este caso? ¿O está ocurriendo un proceso más general?


La respuesta de rethab ya te dio el enlace a la especificación; solo déjame añadir eso

  • puede deshabilitar esto (hacer que la advertencia sea un error) a través del -Xfatal-warnings compilador -Xfatal-warnings
  • obtendrás mejores mensajes con el -Ywarn-value-discard ; para foo3 la advertencia del compilador será el discarded non-Unit value más informativo

Tenga en cuenta que esta conversión "cualquiera a unidad" es un compilador mágico, por lo que ni -Yno-predef ni -Yno-imports lo desactivarán; Necesitas las banderas de arriba. Considero que esto es parte de la especificación del lenguaje un error, ya que si por alguna razón desea este comportamiento dudoso, puede agregar algo como

implicit def any2Unit(a: Any): Unit = ()

mientras que la exclusión requiere un indicador de compilador no compatible (por definición, ya que rompe la especificación).

También recomiendo wartremover , donde tienes this y mucho más.


Voy a responder a la pregunta del título para más cobertura. Unit recibe un tratamiento especial en algunos lugares, más de lo que sucede en esos ejemplos de código. En parte, esto se debe a que Unit es un producto del compilador que se reduce al void en la JVM.

Valor de descarte

Este es el caso más sorprendente para las personas. Siempre que el tipo esperado de algún valor sea Unit , el compilador se une a la Unit al final de la expresión que produce el valor, de acuerdo con el SLS - 6.26.1 :

Si ee tiene algún tipo de valor y el tipo esperado es Unit , ee se convierte al tipo esperado incrustándolo en el término { ee; () } { ee; () } .

Así,

def foo3(): Unit = "foo"

se convierte en:

def foo3(): Unit = { "foo" ; () }

Igualmente,

def foo: Future[Unit] = save(customer1).map(_ => save(customer2))

se convierte en:

def foo: Future[Unit] = save(customer1).map(_ => { save(customer2); () })

El beneficio de esto es que no es necesario que la última declaración de un método tenga el tipo Unit si no lo desea. Sin embargo, este beneficio es pequeño, porque si la última declaración de su método que devuelve la Unit no es una Unit , eso generalmente indica un error, por lo que hay un indicador de advertencia para él ( -Ywarn-value-discard ).

En general, me parece mejor devolver un tipo más específico, si es posible, en lugar de devolver la Unit . Por ejemplo, al guardar en una base de datos, es posible que pueda devolver el valor guardado (tal vez con una nueva ID, o algo así).

Clase de valor

Unit es una clase de valor creada por el compilador de Scala, con una sola instancia (si es necesario crear una instancia de clase). Esto significa que se compila hasta el void primitivo en la JVM, a menos que lo trate como una clase (por ejemplo, ().toString ). Tiene su propia sección en la especificación, SLS - 12.2.13 .

Tipo de bloque vacío

Desde el SLS - 6.11 , se asume que el tipo predeterminado de un bloque vacío es Unit . Por ejemplo:

scala> val x = { } x: Unit = ()

Es igual a

Al comparar una Unit con otra (que debe ser el mismo objeto, ya que solo hay una), el compilador emitirá una advertencia especial para informarle de que algo está mal en su programa.

scala> ().==(()) <console>:12: warning: comparing values of types Unit and Unit using `=='' will always yield true ().==(()) ^ res2: Boolean = true

Fundición

Puedes lanzar cualquier cosa a una Unit , ya que el compilador la optimizará (aunque no me queda claro si el descarte de valor se hace cargo después de la inferencia de tipo).

object Test { val a = "a".asInstanceOf[Unit] val b = a }

se convierte en:

object Test extends Object { def <init>(): Test.type = { Test.super.<init>(); () }; private[this] val a: scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT; <stable> <accessor> def a(): Unit = (); private[this] val b: scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT; <stable> <accessor> def b(): Unit = () }