Общие вопросы о акке и типизации
Вопрос 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 ответ:
Вопрос 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, но все же это разумный и последовательный вариант). Как бы то ни было, у нас есть частичное стирание типа и разбитое соответствие типа.