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
; parafoo3
la advertencia del compilador será eldiscarded 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 esUnit
,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 = ()
}