Общие вопросы об Акке и их типах

Вопрос 1:

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

Вопрос 2:

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

case class Event[T](t: T)

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

Вопрос 3:

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

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

testActor вызывает f при приеме Event:

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

К чему будет задан параметр типа t of 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

Вопрос 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

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