Я портирую класс с Ruby на Scala 2.11, который реализует слияние переменных в сообщениях. Я хотел бы передать массив объектов методу слияния и заставить его искать в каждом объекте ключи, на которые есть ссылки в тексте сообщения.
Ядром этого является метод lookUp (key: String, obj: AnyRef), который получает один ключ для поиска и один объект для поиска ключа. Если объект представляет собой карту, а ключи карты являются либо символом, либо строкой, тогда он будет искать запрошенный ключ на карте. В противном случае он проверит, есть ли у объекта метод с тем же именем ключа, и если да, он вызовет этот метод.
В существующем коде Ruby это сделать тривиально просто:
def look_up(key, obj)
if obj.respond_to?(:has_key?) && obj.has_key?(key)
obj[key]
elsif obj.respond_to?(key)
obj.send(key)
elsif obj.instance_variable_defined?(ivar = "@#{key}")
obj.instance_variable_get(ivar)
end
end
Поскольку это легко сделать, код Ruby также ищет переменную экземпляра с тем же именем. Мне не обязательно нужна эта функциональность в моей версии Scala.
Одна из проблем, с которыми я сталкиваюсь, заключается в том, что в найденных мною примерах требуется, чтобы я знал класс объекта заранее, чтобы я мог вызвать ru.typeOf[MyClass].declaration(ru.TermName("key"))
(где ru
это scala.reflect.runtime.universe
).
Другая проблема заключается в том, что это слияние сообщений может происходить сотни раз в минуту, и отражение кажется медленным и сложным процессом. Если все работает по плану, я, скорее всего, кэширую результаты отражения по типу объекта.
ОБНОВЛЕНИЕ: я думал о чем-то подобном, но разве это излишество? Или нужно правильно фиксировать типы на Карте? Кроме того, это не компилируется. Map
, Symbol
и String
не подходят для их контекста.
def lookUp[T](obj: T, key: String)(implicit tag: ru.TypeTag[T]): Option[String] = tag.tpe match {
case ru.TypeRef(a, Map, List(Symbol, _)) => if (obj.contains(Symbol(key))) Some(obj(Symbol(key)).toString) else None
case ru.TypeRef(a, Map, List(String, _)) => if (obj.contains(key)) Some(obj(key).toString) else None
case _ =>
if (/* obj.key() exists */)
// Some(obj.key().toString)
else
None
}
ОБНОВЛЕНИЕ 2: Мне никогда не приходило в голову, что я могу использовать asInstanceOf
с чем-то вроде Map[String, _]
. Я использовал второй пример кода @johny, чтобы придумать свое решение. Я кэширую имена методов по классам в mutable.HashMap[Class[_], Set[String]]
.
def lookUp(obj: AnyRef, key: String): Option[String] = obj match {
case m: Map[_, _] =>
if (m.asInstanceOf[Map[String, _]].contains(key))
extractValue(m.asInstanceOf[Map[String, _]](key))
else if (m.asInstanceOf[Map[Symbol, _]].contains(Symbol(key)))
extractValue(m.asInstanceOf[Map[Symbol, _]](Symbol(key)))
else
None
case _ =>
val klass = obj.getClass
if (!methodsCache.contains(klass))
methodsCache(klass) = klass.getMethods.toList.filter(_.getParameterCount == 0).map(_.getName).toSet
val methodNames = methodsCache(klass)
if (methodsCache(klass).contains(key))
extractValue(klass.getDeclaredMethod(key).invoke(obj))
else
None
}
def extractValue(obj: Any): Option[String] = obj match {
case null | None => None
case Some(x) => Some(x.toString)
case x => Some(x.toString)
}