Mezclar en un rasgo dinámicamente

Tener un rasgo

trait Persisted { def id: Long } 

¿Cómo implemento un método que acepta una instancia de cualquier clase de caso y devuelve su copia con el rasgo mezclado?

La firma del método se ve así:

 def toPersisted[T](instance: T, id: Long): T with Persisted 

Esto se puede hacer con macros (que son oficialmente parte de Scala desde 2.10.0-M3). Aquí hay un ejemplo esencial de lo que estás buscando .

1) Mi macro genera una clase local que hereda de la clase de caso proporcionada y Persisted, al igual que la new T with Persisted haría. Luego almacena su argumento en caché (para evitar evaluaciones múltiples) y crea una instancia de la clase creada.

2) ¿Cómo sé qué árboles generar? Tengo una aplicación simple, parse.exe que imprime el AST que resulta de analizar el código de entrada. Así que acabo de invocar la parse class Person$Persisted1(first: String, last: String) extends Person(first, last) with Persisted , anoté el resultado y lo reproduje en mi macro. parse.exe es un contenedor para scalac -Xprint:parser -Yshow-trees -Ystop-after:parser . Hay diferentes maneras de explorar AST, lea más en “Metaprogtwigción en Scala 2.10” .

3) Las expansiones de macros se pueden verificar si se proporciona -Ymacro-debug-lite como argumento para scalac. En ese caso, se imprimirán todas las expansiones y podrá detectar los errores Codegen más rápidamente.

editar. Actualizado el ejemplo para 2.10.0-M7

No es posible lograr lo que quieres usando vanilla scala. El problema es que a los mixins les gusta lo siguiente:

 scala> class Foo defined class Foo scala> trait Bar defined trait Bar scala> val fooWithBar = new Foo with Bar fooWithBar: Foo with Bar = $anon$1@10ef717 

crea un Foo with Bar mezclada, pero no se hace en tiempo de ejecución. El comstackdor simplemente genera una nueva clase anónima:

 scala> fooWithBar.getClass res3: java.lang.Class[_ <: Foo] = class $anon$1 

Ver Dynamic mixin en Scala - ¿es posible? para más información.

Lo que estás tratando de hacer se conoce como concatenación de registros, algo que el sistema de tipos de Scala no admite. (Hasta ahora, existen sistemas tipo, como este y este , que proporcionan esta característica).

Creo que las clases de tipos pueden ajustarse a su caso de uso, pero no puedo asegurarlo ya que la pregunta no proporciona suficiente información sobre el problema que está tratando de resolver.

Actualizar

Puede encontrar una solución de trabajo actualizada, que utiliza una API Toolboxes de Scala 2.10.0-RC1 como parte del proyecto SORM .


La siguiente solución se basa en la API de reflexión Scala 2.10.0-M3 y el intérprete de Scala. Crea dinámicamente y almacena en caché las clases heredadas de las clases de casos originales con el rasgo mezclado. Gracias al almacenamiento en caché al máximo, esta solución debe crear dinámicamente solo una clase para cada clase de caso original y reutilizarla más adelante.

Como la nueva API de reflexión no es muy divulgada ni es estable y no hay tutoriales sobre ella, esta solución puede implicar algunas acciones de repulsión estúpidas y caprichos.

El siguiente código fue probado con Scala 2.10.0-M3.

1. Persisted.scala

El rasgo a mezclar. Tenga en cuenta que lo he cambiado un poco debido a las actualizaciones en mi progtwig

 trait Persisted { def key: String } 

2. PersistedEnabler.scala

El objeto de trabajo real

 import tools.nsc.interpreter.IMain import tools.nsc._ import reflect.mirror._ object PersistedEnabler { def toPersisted[T <: AnyRef](instance: T, key: String) (implicit instanceTag: TypeTag[T]): T with Persisted = { val args = { val valuesMap = propertyValuesMap(instance) key :: methodParams(constructors(instanceTag.tpe).head.typeSignature) .map(_.name.decoded.trim) .map(valuesMap(_)) } persistedClass(instanceTag) .getConstructors.head .newInstance(args.asInstanceOf[List[Object]]: _*) .asInstanceOf[T with Persisted] } private val persistedClassCache = collection.mutable.Map[TypeTag[_], Class[_]]() private def persistedClass[T](tag: TypeTag[T]): Class[T with Persisted] = { if (persistedClassCache.contains(tag)) persistedClassCache(tag).asInstanceOf[Class[T with Persisted]] else { val name = generateName() val code = { val sourceParams = methodParams(constructors(tag.tpe).head.typeSignature) val newParamsList = { def paramDeclaration(s: Symbol): String = s.name.decoded + ": " + s.typeSignature.toString "val key: String" :: sourceParams.map(paramDeclaration) mkString ", " } val sourceParamsList = sourceParams.map(_.name.decoded).mkString(", ") val copyMethodParamsList = sourceParams.map(s => s.name.decoded + ": " + s.typeSignature.toString + " = " + s.name.decoded).mkString(", ") val copyInstantiationParamsList = "key" :: sourceParams.map(_.name.decoded) mkString ", " """ class """ + name + """(""" + newParamsList + """) extends """ + tag.sym.fullName + """(""" + sourceParamsList + """) with """ + typeTag[Persisted].sym.fullName + """ { override def copy(""" + copyMethodParamsList + """) = new """ + name + """(""" + copyInstantiationParamsList + """) } """ } interpreter.compileString(code) val c = interpreter.classLoader.findClass(name) .asInstanceOf[Class[T with Persisted]] interpreter.reset() persistedClassCache(tag) = c c } } private lazy val interpreter = { val settings = new Settings() settings.usejavacp.value = true new IMain(settings, new NewLinePrintWriter(new ConsoleWriter, true)) } private var generateNameCounter = 0l private def generateName() = synchronized { generateNameCounter += 1 "PersistedAnonymous" + generateNameCounter.toString } // REFLECTION HELPERS private def propertyNames(t: Type) = t.members.filter(m => !m.isMethod && m.isTerm).map(_.name.decoded.trim) private def propertyValuesMap[T <: AnyRef](instance: T) = { val t = typeOfInstance(instance) propertyNames(t) .map(n => n -> invoke(instance, t.member(newTermName(n)))()) .toMap } private type MethodType = {def params: List[Symbol]; def resultType: Type} private def methodParams(t: Type): List[Symbol] = t.asInstanceOf[MethodType].params private def methodResultType(t: Type): Type = t.asInstanceOf[MethodType].resultType private def constructors(t: Type): Iterable[Symbol] = t.members.filter(_.kind == "constructor") private def fullyQualifiedName(s: Symbol): String = { def symbolsTree(s: Symbol): List[Symbol] = if (s.enclosingTopLevelClass != s) s :: symbolsTree(s.enclosingTopLevelClass) else if (s.enclosingPackageClass != s) s :: symbolsTree(s.enclosingPackageClass) else Nil symbolsTree(s) .reverseMap(_.name.decoded) .drop(1) .mkString(".") } } 

3. Sandbox.scala

La aplicación de prueba

 import PersistedEnabler._ object Sandbox extends App { case class Artist(name: String, genres: Set[Genre]) case class Genre(name: String) val artist = Artist("Nirvana", Set(Genre("rock"), Genre("grunge"))) val persisted = toPersisted(artist, "some-key") assert(persisted.isInstanceOf[Persisted]) assert(persisted.isInstanceOf[Artist]) assert(persisted.key == "some-key") assert(persisted.name == "Nirvana") assert(persisted == artist) // an interesting and useful effect val copy = persisted.copy(name = "Puddle of Mudd") assert(copy.isInstanceOf[Persisted]) assert(copy.isInstanceOf[Artist]) // the only problem: compiler thinks that `copy` does not implement `Persisted`, so to access `key` we have to specify it manually: assert(copy.asInstanceOf[Artist with Persisted].key == "some-key") assert(copy.name == "Puddle of Mudd") assert(copy != persisted) } 

Si bien no es posible componer un objeto DESPUÉS de su creación, puede realizar pruebas muy amplias para determinar si el objeto es de una composición específica utilizando alias de tipo y estructuras de definición:

  type Persisted = { def id: Long } class Person { def id: Long = 5 def name = "dude" } def persist(obj: Persisted) = { obj.id } persist(new Person) 

Cualquier objeto con una def id:Long calificará como persistente.

Alcanzar lo que CREO que estás tratando de hacer es posible con las conversiones implícitas:

  object Persistable { type Compatible = { def id: Long } implicit def obj2persistable(obj: Compatible) = new Persistable(obj) } class Persistable(val obj: Persistable.Compatible) { def persist() = println("Persisting: " + obj.id) } import Persistable.obj2persistable new Person().persist() 
Intereting Posts