implícito - ¿Cuándo usar val o def en los rasgos de Scala?
ejemplos de implícito y explícito (3)
Estaba revisando las diapositivas scala efectivas y menciona en la diapositiva 10 que nunca use val
en un trait
para miembros abstractos y use def
lugar. La diapositiva no menciona en detalle por qué el uso de val
abstracto en un trait
es un antipatrón. Apreciaría si alguien puede explicar las mejores prácticas en torno al uso de val vs def en un rasgo de métodos abstractos
Prefiero no usar val
en los rasgos porque la declaración val tiene un orden de inicialización confuso y no intuitivo. Puede agregar un rasgo a la jerarquía que ya funciona y rompería todo lo que funcionó antes, vea mi tema: por qué usar plain val en clases no finales
Debes tener en cuenta todo lo relacionado con el uso de estas val declaraciones en mente, que eventualmente te llevarán a un error.
Actualización con un ejemplo más complicado
Pero a veces no puedes evitar usar val
. Como @ 0__ había mencionado, a veces necesita un identificador estable y def
no es uno.
Daría un ejemplo para mostrar de qué estaba hablando:
trait Holder {
type Inner
val init : Inner
}
class Access(val holder : Holder) {
val access : holder.Inner =
holder.init
}
trait Access2 {
def holder : Holder
def access : holder.Inner =
holder.init
}
Este código produce el error:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
def access : holder.Inner =
Si se toma un minuto para pensar que entenderá que el compilador tiene un motivo para quejarse. En el caso de Access2.access
no pudo derivar el tipo de devolución de ninguna manera. def holder
significa que podría implementarse de manera amplia. Podría devolver diferentes titulares para cada llamada y los titulares incorporarían diferentes tipos Inner
. Pero la máquina virtual Java espera que se devuelva el mismo tipo.
Una def
puede implementarse mediante un def
, un val
, un lazy val
o un object
. Entonces, es la forma más abstracta de definir un miembro. Dado que los rasgos suelen ser interfaces abstractas, decir que quiere un val
es decir cómo debería hacerlo la implementación. Si solicita un val
, una clase implementadora no puede usar una def
.
Se necesita un valor solo si necesita un identificador estable, por ejemplo, para un tipo dependiente de la ruta. Eso es algo que usualmente no necesitas.
Comparar:
trait Foo { def bar: Int }
object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok
class F2(val bar: Int) extends Foo // ok
object F3 extends Foo {
lazy val bar = { // ok
Thread.sleep(5000) // really heavy number crunching
42
}
}
Si tuvieras
trait Foo { val bar: Int }
no podrías definir F1
o F3
.
Ok, y confundirlo y responder a los val
abstractos de @ om-nom-nom puede causar problemas de inicialización:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko // zero!!
Este es un problema desagradable que, en mi opinión personal, debería desaparecer en futuras versiones de Scala, corrigiéndolo en el compilador, pero sí, actualmente esta también es una razón por la cual no se deben usar val
abstractos.
Editar (enero de 2016): se le permite anular una declaración val
abstracta con una implementación de lazy val
, por lo que también evitaría la falla de inicialización.
Usar siempre def parece un poco incómodo ya que algo como esto no funcionará:
trait Entity { def id:Int}
object Table {
def create(e:Entity) = {e.id = 1 }
}
Obtendrá el siguiente error:
error: value id_= is not a member of Entity