mixin - scala for
¿Cómo mezclar un rasgo con una instancia? (5)
Dado un rasgo MyTrait
:
trait MyTrait {
def doSomething = println("boo")
}
se puede mezclar en una clase con extends
o with
:
class MyClass extends MyTrait
También se puede mezclar al crear una nueva instancia:
var o = new MyOtherClass with MyTrait
o.doSomething
Pero ... ¿puede el rasgo (o cualquier otro si eso hace la diferencia) ser agregado a una instancia existente?
Estoy cargando objetos usando JPA en Java y me gustaría agregarles algunas funcionalidades usando rasgos. ¿Es posible?
Me gustaría poder mezclar un rasgo de la siguiente manera:
var o = DBHelper.loadMyEntityFromDB(primaryKey);
o = o with MyTrait //adding trait here, rather than during construction
o.doSomething
¿Por qué no utilizar Scala''s extender mi patrón de biblioteca?
https://alvinalexander.com/scala/scala-2.10-implicit-class-example
No estoy seguro de cuál es el valor de retorno de:
var o = DBHelper.loadMyEntityFromDB(primaryKey);
pero digamos, es DBEntity
para nuestro ejemplo. Puedes tomar la clase DBEntity y convertirla a una clase que amplíe tu rasgo, MyTrait
.
Algo como:
trait MyTrait {
def doSomething = {
println("boo")
}
}
class MyClass() extends MyTrait
// Have an implicit conversion to MyClass
implicit def dbEntityToMyClass(in: DBEntity): MyClass =
new MyClass()
Creo que también podrías simplificar esto usando solo una clase implícita.
implicit class ConvertDBEntity(in: DBEntity) extends MyTrait
En particular, no me gusta la respuesta aceptada aquí, b / c sobrecarga el operador ::
para mezclar un rasgo.
En Scala, el operador ::
se utiliza para secuencias, es decir:
val x = 1 :: 2 :: 3 :: Nil
Usarlo como un medio de herencia se siente, en mi humilde opinión, un poco incómodo.
¿Qué tal una clase implícita? Me parece más fácil en comparación con la forma en las otras respuestas con una clase interna final y una función "mixin".
trait MyTrait {
def traitFunction = println("trait function executed")
}
class MyClass {
/**
* This inner class must be in scope wherever an instance of MyClass
* should be used as an instance of MyTrait. Depending on where you place
* and use the implicit class you must import it into scope with
* "import mypackacke.MyImplictClassLocation" or
* "import mypackage.MyImplicitClassLocation._" or no import at all if
* the implicit class is already in scope.
*
* Depending on the visibility and location of use this implicit class an
* be placed inside the trait to mixin, inside the instances class,
* inside the instances class'' companion object or somewhere where you
* use or call the class'' instance with as the trait. Probably the
* implicit class can even reside inside a package object. It also can be
* declared private to reduce visibility. It all depends on the structure
* of your API.
*/
implicit class MyImplicitClass(instance: MyClass) extends MyTrait
/**
* Usage
*/
new MyClass().traitFunction
}
Normalmente utilizo un implicit
para mezclar en un nuevo método con un objeto existente.
Mira, si tengo algún código como a continuación:
final class Test {
def f = "Just a Test"
...some other method
}
trait MyTrait {
def doSomething = {
println("boo")
}
}
object HelperObject {
implicit def innerObj(o:MixTest) = o.obj
def mixWith(o:Test) = new MixTest(o)
final class MixTest private[HelperObject](obj:Test) extends MyTrait
}
y luego puede usar el método MyTrait
con un objeto Test ya existente.
val a = new Test
import HelperObject._
val b = HelperObject.mixWith(a)
println(b.f)
b.doSomething
en su ejemplo, puede usar así:
import HelperObject._
val o = mixWith(DBHelper.loadMyEntityFromDB(primaryKey));
o.doSomething
Estoy pensando en una sintaxis prefecto para definir este HelperObject:
trait MyTrait {
..some method
}
object MyTrait {
implicit def innerObj(o:MixTest) = o.obj
def ::(o:Test) = new MixTest(o)
final class MixTest private[MyTrait](obj:Test) extends MyTrait
}
//then you can use it
val a = new Test
val b = a :: MyTrait
b.doSomething
b.f
// for your example
val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait
o.doSomething
Tengo una idea para este uso:
//if I had a class like this
final class Test {
def f = println("foo")
}
trait MyTrait {
def doSomething = {
println("boo")
}
}
object MyTrait {
implicit def innerObj(o:MixTest) = o.obj
def ::(o:Test) = new MixTest(o)
final class MixTest private[MyTrait](val obj:Test) extends MyTrait
}
puedes usar este rasgo de la siguiente manera:
import MyTrait._
val a = new Test
val b = a :: MyTrait
b.doSomething
b.f
para su código de ejemplo:
val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait
o.doSomething
Espero que esto pueda ayudarte.
ACTUALIZADO
object AnyTrait {
implicit def innerObj[T](o: MixTest[T]):T = o.obj
def ::[T](o: T) = new MixTest(o)
final class MixTest[T] private[AnyTrait](val obj: T) extends MyTrait
}
pero este patrón tiene algunos límites, no puede usar algún método de ayuda implícito que ya se haya definido.
val a = new Test
a.f
val b = a :: AnyTrait
b.f1
b.f
val c = "say hello to %s" :: AnyTrait
println(c.intern) // you can invoke String''s method
println(c.format("MyTrait")) //WRONG. you can''t invoke StringLike''s method, though there defined a implicit method in Predef can transform String to StringLike, but implicit restrict one level transform, you can''t transform MixTest to String then to StringLike.
c.f1
val d = 1 :: AnyTrait
println(d.toLong)
d.toHexString // WRONG, the same as above
d.f1
Un objeto de tiempo de ejecución existente en la JVM tiene un cierto tamaño en el montón. Agregarle un rasgo significaría alterar su tamaño en el montón y cambiar su firma.
Entonces, el único camino a seguir sería hacer algún tipo de transformación en tiempo de compilación.
La composición de Mixin en Scala ocurre en tiempo de compilación. Lo que el compilador podría hacer potencialmente es crear un contenedor B alrededor de un objeto existente A con el mismo tipo que simplemente reenvía todas las llamadas al objeto existente A, y luego mezclar un rasgo T con B. Esto, sin embargo, no se implementa. Es cuestionable cuando esto sea posible, ya que el objeto A podría ser una instancia de una clase final, que no se puede extender.
En resumen, la composición mixin no es posible en instancias de objetos existentes.
ACTUALIZADO:
Relacionado con la solución inteligente propuesta por Googol Shan, y generalizándolo para trabajar con cualquier rasgo, esto es lo más lejos que tengo. La idea es extraer la funcionalidad mixin común en el rasgo DynamicMixinCompanion
. El cliente debe crear un objeto complementario que DynamicMixinCompanion
para cada rasgo para el que desee tener la funcionalidad dynamic mixin. Este objeto complementario requiere definir el objeto de rasgo anónimo que se crea ( ::
.
trait DynamicMixinCompanion[TT] {
implicit def baseObject[OT](o: Mixin[OT]): OT = o.obj
def ::[OT](o: OT): Mixin[OT] with TT
class Mixin[OT] protected[DynamicMixinCompanion](val obj: OT)
}
trait OtherTrait {
def traitOperation = println("any trait")
}
object OtherTrait extends DynamicMixinCompanion[OtherTrait] {
def ::[T](o: T) = new Mixin(o) with OtherTrait
}
object Main {
def main(args: Array[String]) {
val a = "some string"
val m = a :: OtherTrait
m.traitOperation
println(m.length)
}
}