Scala 中的类型安全原语
我希望在我的 Scala 代码中拥有类型安全的原语“子类”,而不会受到装箱性能的影响(对于延迟非常低的应用程序)。例如,这样的:
class Timestamp extends Long
class ProductId extends Long
def process(timestamp: Timestamp, productId: ProductId) {
...
}
val timestamp = 1: Timestamp // should not box
val productId = 1: ProductId // should not box
process(timestamp, productId) // should compile
process(productId, timestamp) // should NOT compile
有一个 去年 Scala 用户邮件列表上的帖子似乎得出了没有装箱就不可能实现的结论,但我想知道现在在 Scala 2.8 中这是否可能。
I'd like to have type-safe "subclasses" of primitives in my Scala code without the performance penalty of boxing (for a very low-latency application). For example, something like this:
class Timestamp extends Long
class ProductId extends Long
def process(timestamp: Timestamp, productId: ProductId) {
...
}
val timestamp = 1: Timestamp // should not box
val productId = 1: ProductId // should not box
process(timestamp, productId) // should compile
process(productId, timestamp) // should NOT compile
There was a thread on the Scala User mailing list last year which seemed to conclude it wasn't possible without boxing, but I wonder if this is possible now in Scala 2.8.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
为什么不使用类型别名呢?我知道它们并不完美(即它们不能解决您的编译问题),但它们可以使您的代码更清晰而不影响性能?
然后,您可以编写使用 pimp my library 模式的方法,并让 JVM 使用转义分析来消除运行时开销:
请注意,所有常见的警告都适用:除非您非常确定(因为您正在做超低延迟编程(例如),使用盒装类型对性能的影响可能不是问题。我每天处理的系统处理数千万个输入,没有任何问题!
Why not use type aliases instead? I appreciate that they are not perfect (i.e. they do not solve your compilation issue), but they can make your code clearer without taking the performance hit?
Then you can write methods which use the pimp my library pattern and let the JVM use escape analysis to remove the runtime overhead:
Note that all the usual caveats apply: unless you are extremely sure (because you are doing ultra-low-latency programming e.g.), the performance hit of using the boxed type is probably not a concern. I deal with systems processing tens of millions of inputs a day with no issues whatsoever!
Scala 类型层次结构的根是
Any
,其子级为AnyVal
和Anyref
。所有整数类型(如示例中的Long
)均源自AnyVal
,并且您无法在树的这一侧创建子类。AnyVal
的子代代表 JVM 中的低级类型。类型擦除意味着在运行时实际上不再有AnyVal
,如果您可以创建Timestamp
它也会在运行时丢失。您需要装箱/拆箱作为存储包装类型信息的地方。一个好的 JVM 可以消除运行时的大量装箱/拆箱开销。例如,请参阅在 JVM 上启用逃逸分析的体验
The root of the Scala type hierarchy is
Any
with childrenAnyVal
andAnyref
. All of the integral types (likeLong
in your example) descend fromAnyVal
and you can't create subclasses in that side of the tree. Children ofAnyVal
represent low level types within the JVM. Type-erasure means that at runtime there really is noAnyVal
anymore and if you could makeTimestamp
it would also be lost at runtime. You need boxing/unboxing as a place to store your wrapper type information.A good JVM can eliminate a lot of the boxing/unboxing overhead at runtime. For example, see Experiences with escape analysis enabled on the JVM
从 2.10 开始,现在可以使用 值类:
注释行产生:
我没有一个简单的方法来说服您装箱不会发生,因为像这样的简单示例可以通过编译器优化装箱,但这里是字节码:
This is now possible, as of 2.10, with value classes:
The commented line produces:
I don't have an easy way to convince you that the boxing won't happen, since a simple example like this could have boxing optimized away by the compiler, but here's the byte code:
这种事情可以通过插件来保证。毕竟,Scala 不受支持且无法工作的单位插件在阻止将距离添加到持续时间时做了类似的事情。
This kind of thing could be ensured by a plugin. The unsupported and not-working units plugin for Scala, after all, did something like it when it prevented distances to be added to durations.
原语的概念(在 JVM 上)是预定义和最终的,您不能向 JVM 添加更多原语,只能添加类(Java 中的 java.lang.Object 或 Scala 中的 scala.AnyRef)...
包装器的装箱/拆箱,如 Ben 所提议的,
case class Timestamp(ts: Long)
不应造成严重的性能损失。类型别名,
type Timestamp = Long
,实际上是别名,因此编译器无法区分同一类型的两个别名(Long)。
The concept of primitives (on the JVM) is that they are predefined and final, you cannot add further primitives to the JVM, only classes (java.lang.Object in Java or scala.AnyRef in Scala)...
The boxing/ unboxing of a wrapper, as proposed by Ben,
case class Timestamp(ts: Long)
, shouldn't create a substantial performance penality.Type aliases,
type Timestamp = Long
, are really aliases, so there is no way for the compiler to distinguish two aliases to the same type (Long
).