Сопоставление структурных типов с образцом в Scala

Почему это печатает wtf? Сопоставление с образцом не работает со структурными типами?

  "hello" match {
    case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?")
    case _ => println("okie dokie")
  }

person Mitch Blevins    schedule 01.01.2010    source источник


Ответы (2)


Запуск этого примера в интерпретаторе 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 => ...
}

EDIT: В ответ на ваши комментарии я думал о решении, но вчера не было времени опубликовать его. К сожалению, даже если это должно работать, компилятору не удается внедрить правильный 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, чтобы увидеть, содержит ли объект метод.

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.

person Flaviu Cipcigan    schedule 01.01.2010
comment
Спасибо, Флавиу. Это отвечает на мой вопрос. Но это все еще заставляет меня задаться вопросом, как лучше всего этого добиться, потому что структура — это то, что на самом деле доступно во время выполнения посредством отражения. Просто неловко до него добираться. - person Mitch Blevins; 01.01.2010

Если вам придется использовать отражение, вы можете, по крайней мере, сделать его более красивым с помощью экстрактора:

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")
}
person Kim Stebel    schedule 08.08.2010
comment
Было бы неплохо, если бы можно было определить HasFoo более гибко, например val HasFoo = new Has[{def foo():Unit}]("foo"). Я просто пытался сделать это таким образом, но, похоже, все еще есть некоторые проблемы с более сложными типами, такими как {def foo(i:Int):Int}. - person Debilski; 08.08.2010
comment
Почему компилятор не делает это автоматически? - person Gabriel; 28.04.2017
comment
containsMethod можно обезвожить до Try(x.getClass.getMethod(name, params: _*)).isSuccess - person Oleg Rudenko; 08.06.2017