Tipos estruturais de correspondência de padrões em scala
-
22-09-2019 - |
Pergunta
Por que essa impressão wtf? A correspondência de padrões não funciona em tipos estruturais?
"hello" match {
case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?")
case _ => println("okie dokie")
}
Solução
Executando este exemplo no intérprete de Scala com avisos sem controle em (scala -unchecked
) produz o seguinte aviso: warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure
. Infelizmente, um tipo genérico como esse não pode ser verificado em tempo de execução, pois a JVM não referencia genéricos.
Tudo o que a JVM vê nessa correspondência de padrão é:
"hello" match {
case s: Object => ...
case annon: Object => ...
}
EDITAR: Em resposta aos seus comentários, tenho pensado em uma solução, mas não tive tempo para publicá -la ontem. Infelizmente, mesmo que deve trabalho, o compilador falha em injetar o adequado Manifest
.
O problema que você deseja resolver é comparar se um objeto é de um determinado tipo estrutural. Aqui está algum código em que tenho pensado (Scala 2.8-R20019, como Scala 2.7.6.Final caiu em mim algumas vezes enquanto brincava com idéias semelhantes)
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]
Método isFoo
basicamente compara os manifestos da classe x
do Foo
. Em um mundo ideal, o manifesto de um tipo estrutural deve ser igual ao manifesto de qualquer tipo que contenha os métodos necessários. Pelo menos essa é a minha linha de pensamento. Infelizmente, isso falha em compilar, à medida que o compilador injeta um Manifest[AnyRef]
em vez de um Manifest[Foo]
Ao ligar getManifest[Foo]
. Curiosamente, se você não usar um tipo estrutural (por exemplo, type Foo = String
), esse código compila e funciona conforme o esperado. Postarei uma pergunta em algum momento para ver por que isso falha nos tipos estruturais - é uma decisão de design ou é apenas um problema da API de reflexão experimental.
Falhando nisso, você sempre pode usar a reflexão Java para ver se um objeto contém um método.
def containsMethod(x: AnyRef, name: String, params: java.lang.Class[_]*) = {
try {
x.getClass.getMethod(name, params: _*)
true
}
catch {
case _ => false
}
}
que funciona conforme o esperado:
containsMethod("foo", "concat", classOf[String]) // true
containsMethod("foo", "bar", classOf[List[Int]]) // false
... mas não é muito bom.
Além disso, observe que a estrutura de um tipo estrutural não está disponível em tempo de execução. Se você tem um método def foo(x: {def foo: Int}) = x.foo
, depois de apagar você def foo(x: Object) = [some reflection invoking foo on x]
, as informações de tipo que estão sendo perdidas. É por isso que a reflexão é usada em primeiro lugar, pois você precisa invocar um método em um Object
E a JVM não sabe se o Object
tem esse método.
Outras dicas
Se você precisar usar a reflexão, pode pelo menos parecer mais agradável com um extrator:
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")
}