关于 Akka 和类型安全的一般问题
General Questions about Akka and Typesafety
问题一:
JVM 不了解泛型,因此 Scala(和 Java)中的类型参数只存在于编译时。它们在 运行 时间不存在。由于 Akka 是一个 Scala(和 Java)框架,它也有这个缺点。它尤其受到影响,因为在 Akka 中,参与者之间的消息(显然)仅在 运行 时间内交换,因此这些消息的所有类型参数都丢失了。到目前为止正确吗?
问题二:
假设我定义了以下采用一种类型参数的情况 class:
case class Event[T](t: T)
现在,我实例化一个 Event[Int](42)
并将其发送到我的 testActor
。我的 testActor
基本上收到一个 Event[Any]
并且不知道 t
是什么类型是正确的吗?
问题三:
比如说,在我的 testActor
中有一个函数也接受一个类型参数:
def f[T](t: T) = println(t)
testActor
在收到 Event
:
时调用 f
override def receive: Receive = {
case Event(t) => f(t)
}
这样调用函数时,f
的类型参数T
会设置成什么? Any
?如果是这样,下面的函数是否有效地等同于上面的函数(假设它只会像上面描述的那样被调用):
def f2(t: Any) = println(t)
问题四:
现在,考虑 f
的定义:
def f[T](t: T) = println(t.getClass)
我没有更改调用站点:
override def receive: Receive = {
case Event(t) => f(t)
}
这不应该总是将 Any
打印到控制台吗?但是,当我向 testActor
发送 Event[Int](42)
时,它确实会向控制台打印 java.lang.Integer
。所以类型信息毕竟没有被删除?我很困惑。
问题 1
将类型擦除称为 "shortcoming" 似乎有点像是在回避问题,但无论如何,这一段对我来说听起来很合理,也许对清单和 class 标签以及什么 "exists" 的意思。 :)
问题 2
不完全是。考虑以下类似情况 class 和方法:
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
调用 f(t)
时推断的 T
将是某种存在类型,因此不完全是 Any
,但在这种情况下在道德上等同于它。但是,正如上面的 Foo
案例所示,如果 Event
有涉及 T
的其他成员或方法,编译器 会 知道它是相同的 T
.
问题 4
当我们说 JVM 擦除类型时,我们实际上只是指 "in generic contexts"。每个对象(在 JVM 意义上)都有一个与之关联的 class:
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
这里有两点需要注意。首先,我们得到的 class 值是几个子类型关系,如果我们不使用 : Any
归因(我是 hand-waving a bit 通过比较 classes 和类型,但你知道我的意思)。其次,由于泛型的类型擦除,我们没有从 y.getClass
中获得任何关于元素类型的信息,只是值的 "top-level" class。
结论
在我看来,就类型擦除而言,这是所有可能世界中最糟糕的一种。当然,您可以在 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-style 具体化泛型(在这种情况下我可能不会使用 Scala,但它仍然是一个合理且一致的选择)。事实上,我们有部分类型擦除和损坏的类型大小写匹配。
问题一:
JVM 不了解泛型,因此 Scala(和 Java)中的类型参数只存在于编译时。它们在 运行 时间不存在。由于 Akka 是一个 Scala(和 Java)框架,它也有这个缺点。它尤其受到影响,因为在 Akka 中,参与者之间的消息(显然)仅在 运行 时间内交换,因此这些消息的所有类型参数都丢失了。到目前为止正确吗?
问题二:
假设我定义了以下采用一种类型参数的情况 class:
case class Event[T](t: T)
现在,我实例化一个 Event[Int](42)
并将其发送到我的 testActor
。我的 testActor
基本上收到一个 Event[Any]
并且不知道 t
是什么类型是正确的吗?
问题三:
比如说,在我的 testActor
中有一个函数也接受一个类型参数:
def f[T](t: T) = println(t)
testActor
在收到 Event
:
f
override def receive: Receive = {
case Event(t) => f(t)
}
这样调用函数时,f
的类型参数T
会设置成什么? Any
?如果是这样,下面的函数是否有效地等同于上面的函数(假设它只会像上面描述的那样被调用):
def f2(t: Any) = println(t)
问题四:
现在,考虑 f
的定义:
def f[T](t: T) = println(t.getClass)
我没有更改调用站点:
override def receive: Receive = {
case Event(t) => f(t)
}
这不应该总是将 Any
打印到控制台吗?但是,当我向 testActor
发送 Event[Int](42)
时,它确实会向控制台打印 java.lang.Integer
。所以类型信息毕竟没有被删除?我很困惑。
问题 1
将类型擦除称为 "shortcoming" 似乎有点像是在回避问题,但无论如何,这一段对我来说听起来很合理,也许对清单和 class 标签以及什么 "exists" 的意思。 :)
问题 2
不完全是。考虑以下类似情况 class 和方法:
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
调用 f(t)
时推断的 T
将是某种存在类型,因此不完全是 Any
,但在这种情况下在道德上等同于它。但是,正如上面的 Foo
案例所示,如果 Event
有涉及 T
的其他成员或方法,编译器 会 知道它是相同的 T
.
问题 4
当我们说 JVM 擦除类型时,我们实际上只是指 "in generic contexts"。每个对象(在 JVM 意义上)都有一个与之关联的 class:
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
这里有两点需要注意。首先,我们得到的 class 值是几个子类型关系,如果我们不使用 : Any
归因(我是 hand-waving a bit 通过比较 classes 和类型,但你知道我的意思)。其次,由于泛型的类型擦除,我们没有从 y.getClass
中获得任何关于元素类型的信息,只是值的 "top-level" class。
结论
在我看来,就类型擦除而言,这是所有可能世界中最糟糕的一种。当然,您可以在 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-style 具体化泛型(在这种情况下我可能不会使用 Scala,但它仍然是一个合理且一致的选择)。事实上,我们有部分类型擦除和损坏的类型大小写匹配。