Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

General Questions about Akka and Typesafety

Question 1:

The JVM does not know about generics, therefore type parameters in Scala (and Java) only exist at compile-time. They don't exist at run-time. Since Akka is a Scala (and Java) framework, it suffers from this very shortcoming, too. It suffers from it in particular because in Akka, messages between actors are (obviously) only exchanged during run-time, so all the type arguments of those very messages are lost. Correct so far?

Question 2:

Say I defined the following case class that takes one type parameter:

case class Event[T](t: T)

Now, I instantiate an Event[Int](42) and send it to my testActor. Is it correct that my testActor basically receives an Event[Any] and has no idea of what type t is?

Question 3:

Say, inside my testActor there exists a function that also takes a type parameter:

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

The testActor calls f when receiving an Event:

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

To what will the type parameter T of f be set when the function is called like this? Any? If so, would the following function effectively be equivalent to the above (assuming it would only get called like described above):

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

Question 4:

Now, consider this definition of f:

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

I didn't change the call-site:

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

Shouldn't this always print Any to the console? When I send an Event[Int](42) to my testActor, it does print java.lang.Integer to the console, though. So the type information isn't erased after all? I am confused.

like image 591
lkbaerenfaenger Avatar asked Jan 27 '17 23:01

lkbaerenfaenger


Video Answer


1 Answers

Question 1

Calling type erasure a "shortcoming" seems kind of like begging the question, but whatever, this paragraph sounds fairly reasonable to me, maybe with some quibbles about manifests and class tags and what "exists" means. :)

Question 2

Not exactly. Consider the following similar case class and method:

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")
}

This works just fine:

scala> doSomething(Foo("hello world", (_: String).size))
11

So we're not just seeing the Foo as a Foo[Any], since the (_: String).size is not a valid 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
                                                ^

So the compiler knows something about the types of the members.

Question 3

The inferred T when you call f(t) will be some kind of existential type, so not exactly Any, but in this case morally equivalent to it. As the Foo case above shows, though, if Event had other members or methods involving T the compiler would know that it's the same T.

Question 4

When we say the JVM erases types, we really just mean "in generic contexts". Every object (in the JVM sense) has a class associated with it:

scala> val x: Any = "foo"
x: Any = foo

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

But…

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

There are two things to note here. First, the class value we get is a couple of subtype relationships more specific than even the inferred type would have been if we'd left off the : Any ascription (I'm hand-waving a bit by comparing classes and types, but you know what I mean). Second, because of type erasure for generics, we don't get any information about the element type from y.getClass, just the "top-level" class of the value.

Conclusion

In my view this is kind of the worst of all possible worlds as far as type erasure is concerned. Sure you can dispatch on types at runtime in 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")
}

And then:

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

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

But then:

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

I'd personally prefer complete erasure of types at runtime (at least as far as the programmer can see) and no type case matching at all. Alternatively we could have .NET-style reified generics (in which case I probably wouldn't be using Scala, but still, it's a reasonable and consistent option). As it is we've got partial type erasure and broken type case matching.

like image 196
Travis Brown Avatar answered Oct 11 '22 10:10

Travis Brown