Общие вопросы о акке и типизации


Вопрос 1:

JVM не знает о генераторах, поэтому параметры типа в Scala (и Java) существуют только во время компиляции. Они не существуют во время выполнения. Поскольку Akka-это платформа Scala (и Java), она также страдает от этого недостатка. Он страдает от этого, в частности, потому, что в Akka сообщения между акторами (очевидно) обмениваются только во время выполнения, поэтому все аргументы типа этих самых сообщений теряются. Правильно так далеко?

Вопрос 2:

Скажем, я определил следующий класс case, который принимает один параметр типа:

case class Event[T](t: T)
Теперь я создаю экземпляр Event[Int](42) и отправляю его в свой testActor. Верно ли, что мой testActor в основном получает Event[Any] и не имеет представления о том, какой тип t?

Вопрос 3:

Скажем, внутри моей testActor существует функция, которая также принимает параметр типа:

def f[T](t: T) = println(t)

Вызовы testActor f при получении Event:

override def receive: Receive = {
  case Event(t) => f(t)
}

Чему будет соответствовать параметр типа T из f, когда функция вызывается подобным образом? Any? Если да, то будет ли следующая функция эффективно эквивалентна вышеописанной (предполагая, что она будет вызвана только так, как описано выше):

def f2(t: Any) = println(t)

Вопрос 4:

Теперь рассмотрим следующее определение f:
def f[T](t: T) = println(t.getClass)

Я не менял место вызова:

override def receive: Receive = {
  case Event(t) => f(t)
}

Разве это не должно всегда выводить Any на консоль? Когда я посылаю сообщение Event[Int](42) на мой testActor, он все же выводит java.lang.Integer на консоль. Значит, информация о типе все-таки не стирается? Я в замешательстве.

1 10

1 ответ:

Вопрос 1

Называть стирание типов "недостатком", похоже, напрашивается вопрос, но как бы то ни было, этот абзац звучит довольно разумно для меня, возможно, с некоторыми придирками по поводу манифестов и тегов классов и того, что означает" существует". :)

Вопрос 2

Не совсем так. Рассмотрим следующий аналогичный класс и метод case:

case class Foo[T](v: T, f: T => Int)

def doSomething(x: Any): Unit = x match {
  case Foo(v, f) => println(f(v))
  case _ => println("whatever")
}

Это работает просто отлично:

scala> doSomething(Foo("hello world", (_: String).size))
11
Таким образом, мы не просто рассматриваем Foo как Foo[Any], поскольку (_: String).size является не действительный Any => Int:
scala> val stringSize: Any => Int = (_: String).size
<console>:11: error: type mismatch;
 found   : String => Int
 required: Any => Int
       val stringSize: Any => Int = (_: String).size
                                                ^
Таким образом, компилятор знаетчто-то о типах членов.

Вопрос 3

Выводимое T при вызове f(t) будет неким экзистенциальным типом, поэтому не совсем Any, но в данном случае морально эквивалентно ему. Однако, как показано в приведенном выше примере Foo, если бы Event имел другие члены или методы, включающие T, компилятор знал бы, что это то же самоеT.

Вопрос 4

Когда мы говорим, что JVM стирает типы, мы на самом деле просто подразумеваем "в общих контекстах". Каждый объект (в смысле JVM) имеет класс, связанный с ним:
scala> val x: Any = "foo"
x: Any = foo

scala> x.getClass
res0: Class[_] = class java.lang.String

Но ...

scala> val y: Any = Seq(1, 2, 3)
y: Any = List(1, 2, 3)

scala> y.getClass
res1: Class[_] = class scala.collection.immutable.$colon$colon

Есть две вещи, чтобы отметить здесь. Во-первых, значение класса, которое мы получаем, - это пара отношений подтипов, более специфичных, чем даже предполагаемый тип, если бы мы оставили описание : Any (я немного размахиваю руками, сравнивая классы и типы, но вы знаете, что я имею в виду). Во-вторых, потому что стирание типов для дженериков, мы не получаем никакой информации о типе элемента из y.getClass, только класс" верхнего уровня " значения.

Заключение

На мой взгляд, это худший из всех возможных миров, что касается стирания типов. Конечно, вы можете отправлять типы во время выполнения в Scala!
def foo(x: Any): Unit = x match {
  case s: String => println(s"I got a string: $s")
  case d: Double => println("numbers suck!")
  case xs: List[Int] => println(f"first int is ${ xs.head }%d")
  case _ => println("something else")
}

А затем:

scala> foo("bar")
I got a string: bar

scala> foo(List(1, 2, 3))
first int is 1

Но тогда:

scala> foo(List(true, false))
java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.Integer
  at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
  at .foo(<console>:15)
  ... 31 elided

Лично я предпочел бы полное стирание типов во время выполнения (по крайней мере, в том, что касается программиста может видеть) и никакого соответствия типа case вообще. В качестве альтернативы мы могли бы использовать обобщенные генераторы в стиле .NET (в этом случае я, вероятно, не буду использовать Scala, но все же это разумный и последовательный вариант). Как бы то ни было, у нас есть частичное стирание типа и разбитое соответствие типа.