Scala 中的类型安全原语

发布于 2024-10-04 03:53:12 字数 629 浏览 3 评论 0原文

我希望在我的 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

柠檬 2024-10-11 03:53:12

为什么不使用类型别名呢?我知道它们并不完美(即它们不能解决您的编译问题),但它们可以使您的代码更清晰而不影响性能?

type Timestamp = Long
type ProductId = Long

然后,您可以编写使用 pimp my library 模式的方法,并让 JVM 使用转义分析来消除运行时开销:

class RichTimestamp(self: Timestamp) {
  //whatever you want here
}

请注意,所有常见的警告都适用:除非您非常确定(因为您正在做超低延迟编程(例如),使用盒装类型对性能的影响可能不是问题。我每天处理的系统处理数千万个输入,没有任何问题!

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?

type Timestamp = Long
type ProductId = Long

Then you can write methods which use the pimp my library pattern and let the JVM use escape analysis to remove the runtime overhead:

class RichTimestamp(self: Timestamp) {
  //whatever you want here
}

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!

[浮城] 2024-10-11 03:53:12

Scala 类型层次结构的根是 Any,其子级为 AnyValAnyref。所有整数类型(如示例中的 Long)均源自 AnyVal,并且您无法在树的这一侧创建子类。 AnyVal 的子代代表 JVM 中的低级类型。类型擦除意味着在运行时实际上不再有 AnyVal ,如果您可以创建 Timestamp 它也会在运行时丢失。您需要装箱/拆箱作为存储包装类型信息的地方。

case class Timestamp(ts: Long)

一个好的 JVM 可以消除运行时的大量装箱/拆箱开销。例如,请参阅在 JVM 上启用逃逸分析的体验

The root of the Scala type hierarchy is Any with children AnyVal and Anyref. All of the integral types (like Long in your example) descend from AnyVal and you can't create subclasses in that side of the tree. Children of AnyVal represent low level types within the JVM. Type-erasure means that at runtime there really is no AnyVal anymore and if you could make Timestamp it would also be lost at runtime. You need boxing/unboxing as a place to store your wrapper type information.

case class Timestamp(ts: Long)

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

我只土不豪 2024-10-11 03:53:12

从 2.10 开始,现在可以使用 值类

object ValueClasses {
  case class Timestamp(timestamp: Long) extends AnyVal
  case class ProductId(productId: Long) extends AnyVal

  def process(timestamp: Timestamp, productId: ProductId): Unit =
    println(s"$timestamp $productId")

  def main(args: Array[String]): Unit = {
    val timestamp = Timestamp(1) // should not box
    val productId = ProductId(1) // should not box

    process(timestamp, productId) // should compile
//  process(productId, timestamp) // should NOT compile
  }
}

注释行产生:

type mismatch;
 found   : ValueClasses.ProductId
 required: ValueClasses.Timestamp
  process(productId, timestamp) // should NOT compile

我没有一个简单的方法来说服您装箱不会发生,因为像这样的简单示例可以通过编译器优化装箱,但这里是字节码:

  public void process(long, long);
…

  public void main(java.lang.String[]);
    Code:
       0: lconst_1
       1: lstore_2
       2: lconst_1
       3: lstore        4
       5: aload_0
       6: lload_2
       7: lload         4
       9: invokevirtual #65                 // Method process:(JJ)V
      12: return

This is now possible, as of 2.10, with value classes:

object ValueClasses {
  case class Timestamp(timestamp: Long) extends AnyVal
  case class ProductId(productId: Long) extends AnyVal

  def process(timestamp: Timestamp, productId: ProductId): Unit =
    println(s"$timestamp $productId")

  def main(args: Array[String]): Unit = {
    val timestamp = Timestamp(1) // should not box
    val productId = ProductId(1) // should not box

    process(timestamp, productId) // should compile
//  process(productId, timestamp) // should NOT compile
  }
}

The commented line produces:

type mismatch;
 found   : ValueClasses.ProductId
 required: ValueClasses.Timestamp
  process(productId, timestamp) // should NOT compile

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:

  public void process(long, long);
…

  public void main(java.lang.String[]);
    Code:
       0: lconst_1
       1: lstore_2
       2: lconst_1
       3: lstore        4
       5: aload_0
       6: lload_2
       7: lload         4
       9: invokevirtual #65                 // Method process:(JJ)V
      12: return
请别遗忘我 2024-10-11 03:53:12

这种事情可以通过插件来保证。毕竟,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.

源来凯始玺欢你 2024-10-11 03:53:12

原语的概念(在 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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文