Сопоставление структурных типов в Scala
-
22-09-2019 - |
Вопрос
Почему эта печатает WTF? Разве сопоставление шаблонов не работает на структурных типах?
"hello" match {
case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?")
case _ => println("okie dokie")
}
Решение
Запустить этот пример в интерпретаторе Scala с неконтролируемыми предупреждениями (scala -unchecked
) создает следующее предупреждение: warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure
. Анкет К сожалению, такой общий тип не может быть проверен во время выполнения, так как у JVM нет урегулированных дженериков.
Все, что JVM видит в этом матче, это:
"hello" match {
case s: Object => ...
case annon: Object => ...
}
РЕДАКТИРОВАТЬ: В ответ на ваши комментарии я думал о решении, но у меня не было времени, чтобы опубликовать его вчера. К сожалению, даже если это должен работать, компилятор не внесет правильного Manifest
.
Проблема, которую вы хотите решить, состоит в том, чтобы сравнить, имеет ли объект заданный структурный тип. Вот какой-то код, о котором я думал (Scala 2.8-R20019, так как Scala 2.7.6.final разбил меня пару раз, играя с аналогичными идеями)
type Foo = AnyRef { def doesNotExist(i: Int, x: List[_]): Double }
def getManifest[T](implicit m: Manifest[T]) = m
def isFoo[T](x: T)(implicit mt: Manifest[T]) =
mt == getManifest[Foo]
Метод isFoo
в основном сравнивает проявления класса x
из Foo
. Анкет В идеальном мире манифест структурного типа должен быть равным проявлению любого типа, содержащего необходимые методы. По крайней мере, это мой ход мыслей. К сожалению, это не может компилировать, так как компилятор вводит Manifest[AnyRef]
вместо Manifest[Foo]
При звонке getManifest[Foo]
. Анкет Интересно, что если вы не используете структурный тип (например,, например, type Foo = String
), этот код компилирует и работает, как и ожидалось. В какой -то момент я опубликую вопрос, чтобы понять, почему это терпит неудачу со структурными типами - это дизайнерское решение, или это просто проблема экспериментального API отражения.
В противном случае вы всегда можете использовать Java Reflection, чтобы увидеть, содержит ли объект метод.
def containsMethod(x: AnyRef, name: String, params: java.lang.Class[_]*) = {
try {
x.getClass.getMethod(name, params: _*)
true
}
catch {
case _ => false
}
}
что работает как и ожидалось:
containsMethod("foo", "concat", classOf[String]) // true
containsMethod("foo", "bar", classOf[List[Int]]) // false
... но это не очень мило.
Кроме того, обратите внимание, что структура структурного типа недоступна во время выполнения. Если у вас есть метод def foo(x: {def foo: Int}) = x.foo
, после стирания вы получите def foo(x: Object) = [some reflection invoking foo on x]
, тип информации теряется. Вот почему отражение используется в первую очередь, так как вам нужно вызвать метод на Object
И JVM не знает, если Object
имеет этот метод.
Другие советы
Если вам придется использовать отражение, вы можете, по крайней мере, сделать его лучше с экстрактором:
object WithFoo {
def foo(){
println("foo was called")
}
}
object HasFoo {
def containsMethod(x: AnyRef, name: String, params: Array[java.lang.Class[_]]) : Boolean = {
try {
x.getClass.getMethod(name, params: _*)
true
} catch {
case _ => false
}
}
def unapply(foo:AnyRef):Option[{def foo():Unit}] = {
if (containsMethod(foo, "foo", new Array[Class[_]](0))) {
Some(foo.asInstanceOf[{def foo():Unit}])
} else None
}
}
WithFoo.asInstanceOf[AnyRef] match {
case HasFoo(foo) => foo.foo()
case _ => println("no foo")
}