Размышление о кейсе Scala - PullRequest
       38

Размышление о кейсе Scala

12 голосов
/ 08 февраля 2010

Я пытаюсь написать признак (в Scala 2.8), который можно смешивать с классом дел, позволяя проверять его поля во время выполнения для конкретной цели отладки. Я хочу вернуть их в том порядке, в котором они были объявлены в исходном файле, и я хотел бы пропустить любые другие поля внутри класса case. Например:

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))

Вышеприведенная реализация явно ошибочна, потому что она угадывает отношение между положением поля в Продукте и его именем по равенству значения в этом поле, так что, скажем, следующее не будет работать:

Colour(0, 0, 0).getFields

Есть ли способ, которым это может быть реализовано?

Ответы [ 4 ]

10 голосов
/ 11 февраля 2010

Загляните в багажник, и вы найдете это. Послушайте комментарий, это не поддерживается: но так как мне также нужны были эти имена ...

/** 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
  }
}
9 голосов
/ 27 марта 2013

Вот краткая и рабочая версия, основанная на примере выше

  trait CaseClassReflector extends Product {
    def getFields = getClass.getDeclaredFields.map(field => {
      field setAccessible true
      field.getName -> field.get(this)
    })
  }
7 голосов
/ 08 февраля 2010

В каждом примере, который я видел, поля расположены в обратном порядке: последний элемент в массиве getFields является первым, перечисленным в классе case. Если вы используете "casely" классы case, то вы просто сможете отобразить productElement(n) на getDeclaredFields()( getDeclaredFields.length-n-1).

Но это довольно опасно, поскольку я ничего не знаю в спецификации, которая настаивает на том, что так должно быть, и если вы переопределите val в классе case, он даже не появится в getDeclaredFields ( появится в полях этого суперкласса).

Вы можете изменить свой код, предполагая, что все так, но убедитесь, что метод getter с этим именем и productIterator возвращают одно и то же значение и выдают исключение, если они этого не делают (что означает, что вы на самом деле не знаете что соответствует чему).

4 голосов
/ 06 сентября 2011

Вы также можете использовать ProductCompletion из пакета интерпретатора, чтобы получить имена атрибутов и значения классов дел:

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

Редактировать: подсказки от roland и virtualeyes

Необходимо включить библиотеку scalap, которая входит в коллекцию scala-lang .

Спасибо за ваши подсказки, roland и virtualeyes.

...