sintaxis introduccion instanciar crear codigos clases clase scala self-type abstract-type

introduccion - ¿Cómo usar el tipeo de Scala, los tipos abstractos, etc. para implementar un tipo de Self?



scala introduccion (2)

Este es el caso de uso exacto de this.type . Sería como:

def setOption(...): this.type = { // Do stuff ... this }

No pude encontrar la respuesta a esto en ninguna otra pregunta. Supongamos que tengo una superclase abstracta Abstract0 con dos subclases, Concrete1 y Concrete1. Quiero ser capaz de definir en Abstract0 algo así como

def setOption(...): Self = {...}

donde el Ser sería el subtipo concreto. Esto permitiría encadenar llamadas a setOption de la siguiente manera:

val obj = new Concrete1.setOption(...).setOption(...)

y aún obtener Concrete1 como el tipo inferido de obj.

Lo que no quiero es definir esto:

abstract class Abstract0[T <: Abstract0[T]]

porque hace que sea más difícil para los clientes manejar este tipo. Intenté varias posibilidades, incluido un tipo abstracto:

abstract class Abstract0 { type Self <: Abstract0 } class Concrete1 extends Abstract0 { type Self = Concrete1 }

pero luego es imposible implementar setOption, porque this en Abstract0 no tiene el tipo Self. Y al usar this: Self => tampoco funciona en Abstract0.

¿Qué soluciones hay para este problema?


Esto es para lo que this.type es:

scala> abstract class Abstract0 { | def setOption(j: Int): this.type | } defined class Abstract0 scala> class Concrete0 extends Abstract0 { | var i: Int = 0 | def setOption(j: Int) = {i = j; this} | } defined class Concrete0 scala> (new Concrete0).setOption(1).setOption(1) res72: Concrete0 = Concrete0@a50ea1

Como puede ver, setOption devuelve el tipo real utilizado, no Abstract0. Si Concrete0 hubiera setOtherOption entonces (new Concrete0).setOption(1).setOtherOption(...) funcionaría

ACTUALIZACIÓN: para responder a la pregunta de seguimiento de JPP en el comentario (cómo devolver instancias nuevas: El enfoque general descrito en la pregunta es el correcto (utilizando tipos abstractos). Sin embargo, la creación de las nuevas instancias debe ser explícita para cada subclase.

Un enfoque es:

abstract class Abstract0 { type Self <: Abstract0 var i = 0 def copy(i: Int) : Self def setOption(j: Int): Self = copy(j) } class Concrete0(i: Int) extends Abstract0 { type Self = Concrete0 def copy(i: Int) = new Concrete0(i) }

Otro es seguir el patrón de construcción utilizado en la biblioteca de colección de Scala. Es decir, setOption recibe un parámetro de constructor implícito. Esto tiene las ventajas de que la creación de la nueva instancia se puede hacer con más métodos que simplemente ''copiar'' y que se pueden realizar compilaciones complejas. Por ejemplo, setSpecialOption puede especificar que la instancia de retorno debe ser SpecialConcrete.

Aquí hay una ilustración de la solución:

trait Abstract0Builder[To] { def setOption(j: Int) def result: To } trait CanBuildAbstract0[From, To] { def apply(from: From): Abstract0Builder[To] } abstract class Abstract0 { type Self <: Abstract0 def self = this.asInstanceOf[Self] def setOption[To <: Abstract0](j: Int)(implicit cbf: CanBuildAbstract0[Self, To]): To = { val builder = cbf(self) builder.setOption(j) builder.result } } class Concrete0(i: Int) extends Abstract0 { type Self = Concrete0 } object Concrete0 { implicit def cbf = new CanBuildAbstract0[Concrete0, Concrete0] { def apply(from: Concrete0) = new Abstract0Builder[Concrete0] { var i = 0 def setOption(j: Int) = i = j def result = new Concrete0(i) } } } object Main { def main(args: Array[String]) { val c = new Concrete0(0).setOption(1) println("c is " + c.getClass) } }

ACTUALIZACIÓN 2: Respondiendo al segundo comentario de JPP. En el caso de varios niveles de anidación, utilice un parámetro de tipo en lugar de escribir miembro y hacer Abstract0 en un rasgo:

trait Abstract0[+Self <: Abstract0[_]] { // ... } class Concrete0 extends Abstract0[Concrete0] { // .... } class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] { // .... }