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")
  }
Foi útil?

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")
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top