2010-02-08 10 views
12

Estoy intentando escribir un rasgo (en Scala 2.8) que puede ser mezclado en una clase de caso, que permite a sus campos a ser inspeccionada en tiempo de ejecución, para un propósito particular, la depuración. Quiero recuperarlos en el orden en que fueron declarados en el archivo fuente, y me gustaría omitir cualquier otro campo dentro de la clase de caso. Por ejemplo:Reflexión sobre una clase caso Scala

trait CaseClassReflector extends Product { 

    def getFields: List[(String, Any)] = { 
    var fieldValueToName: Map[Any, String] = Map() 
    for (field <- getClass.getDeclaredFields) { 
     field.setAccessible(true) 
     fieldValueToName += (field.get(this) -> field.getName) 
    } 
    productIterator.toList map { value => fieldValueToName(value) -> value } 
    } 

} 

case class Colour(red: Int, green: Int, blue: Int) extends CaseClassReflector { 
    val other: Int = 42 
} 

scala> val c = Colour(234, 123, 23) 
c: Colour = Colour(234,123,23) 

scala> val fields = c.getFields  
fields: List[(String, Any)] = List((red,234), (green,123), (blue,23)) 

La implementación anterior está claramente viciado porque adivina la relación entre la posición de un campo en el producto y su nombre por la igualdad del valor que en el campo, por lo que el siguiente, por ejemplo, no va a funcionar :

Colour(0, 0, 0).getFields 

¿Hay alguna manera de que esto se pueda implementar?

+0

Hay un error en el código. Los valores no son únicos y, por lo tanto, va a sobrescribir los valores con (field.get (this) -> field.getName) cuando se mueva de lo que un campo tiene un nombre de pila. Vea una versión reescrita de su código a continuación. –

+0

@SagieDavidovich De hecho, como se señaló, "la implementación anterior es claramente defectuosa" –

Respuesta

7

En cada ejemplo que he visto, los campos están en orden inverso: el último elemento de la matriz getFields es el primero en la lista de casos. Si usa las clases de casos "muy bien", entonces debería poder mapear productElement(n) en getDeclaredFields()(getDeclaredFields.length-n-1).

Pero esto es bastante peligroso, ya que no conozco nada en la especificación que insista en que debe ser así, y si anulas un valor en la clase de caso, ni siquiera aparecerá en getDeclaredFields (aparecerá en los campos de esa superclase).

Puede cambiar el código para suponer que es así, pero compruebe que el método getter con ese nombre y el productIterator devuelvan el mismo valor y emitan una excepción si no lo hacen (lo que significa que no lo hace en realidad saber qué corresponde a qué)

+1

Estoy enfrentando el mismo problema que Matt R estaba enfrentando. Siendo un novato relativo en Scala, ¿puede explicar su respuesta un poco más? Eso sería bastante útil. ¡Gracias! –

+2

@Core_Dumped - En realidad, creo que es más probable que se meta en problemas que encontrar una solución a su problema a menos que pueda usar mis vagas sugerencias para crear su propia solución. Como dije, "esto es bastante peligroso".Es su responsabilidad poder anticipar y evitar los problemas, lo que puede requerir la transición de "novato relativo" a "no", al menos en este aspecto. ¡Juegue con la reflexión y las clases de casos de muestra en el REPL y vea si puede resolverlo! –

+0

getClass.getDeclaredFields.map (_. GetName) .zip (productIterator.toList) .toMap –

10

Busca en el maletero y encontrarás esto. Escuchar el comentario, esto no es compatible: pero ya que también necesitaba esos nombres ...

/** private[scala] so nobody gets the idea this is a supported interface. 
*/ 
private[scala] def caseParamNames(path: String): Option[List[String]] = { 
    val (outer, inner) = (path indexOf '$') match { 
    case -1 => (path, "") 
    case x => (path take x, path drop (x + 1)) 
    } 

    for { 
    clazz <- getSystemLoader.tryToLoadClass[AnyRef](outer) 
    ssig <- ScalaSigParser.parse(clazz) 
    } 
    yield { 
    val f: PartialFunction[Symbol, List[String]] = 
     if (inner.isEmpty) { 
     case x: MethodSymbol if x.isCaseAccessor && (x.name endsWith " ") => List(x.name dropRight 1) 
     } 
     else { 
     case x: ClassSymbol if x.name == inner => 
      val xs = x.children filter (child => child.isCaseAccessor && (child.name endsWith " ")) 
      xs.toList map (_.name dropRight 1) 
     } 

    (ssig.symbols partialMap f).flatten toList 
    } 
} 
4

También puede utilizar el ProductCompletion del paquete intérprete para llegar a los nombres de atributos y valores de las clases de casos:

import tools.nsc.interpreter.ProductCompletion 

// get attribute names 
new ProductCompletion(Colour(1, 2, 3)).caseNames 
// returns: List(red, green, blue) 

// get attribute values 
new ProductCompletion(Colour(1, 2, 3)).caseFields 

Editar: consejos de Roland y virtualeyes

Es necesario incluir el scalap biblioteca que es parte de scala-lang collection.

Gracias por su consejos, Roland y virtualeyes.

+1

Tenga en cuenta que la llamada a 'caseNames' solo funciona si scalap (http://www.scala-lang.org/node/292) se puede encontrar en classpath. De lo contrario, se devolverá una lista vacía (cuando se usa Scala 2.9.1). –

+1

+1 @roland, es cierto lo que dices. Dado que la descarga de scalap no sale exactamente en una búsqueda de Google, aquí está para 2.9.1: https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/scalap/2.9. 1/ – virtualeyes

+0

tenga en cuenta la captura 22 de la necesidad de una instancia de clase de caso antes de poder reflexionar sobre ella. Esperemos que 2.10 traiga los productos por así decirlo en este sentido, las manos están atadas en 2.9, trabajando con cajas de caja negras es un PITA, termina escribiendo el modelo de dominio, mapeo ORM y validación por triplicado, wtf ... – virtualeyes

9

Aquí hay una versión corta y de trabajo, basado en el ejemplo anterior

trait CaseClassReflector extends Product { 
    def getFields = getClass.getDeclaredFields.map(field => { 
     field setAccessible true 
     field.getName -> field.get(this) 
    }) 
    } 
+0

I Intenté esto y funcionó, al menos para una clase de caso simple. –

Cuestiones relacionadas