6

假设我想要一个像 Java 这样的类Date。它唯一的数据成员是一个 long 表示自 1970 年以来的毫秒数。

仅创建一个新的 Scala 类型会/是否有任何性能优势:

type PrimitiveDate = Long

然后您可以使用隐式转换添加方法,就像对 int with 所做的那样RichInt。这种将原始类型“装箱”到富类中是否涉及任何开销(类创建)?基本上你可以有一个静态方法

def addMonth(date: PrimitiveDate, months: Int): PrimitiveDate = date + 2592000000 * months

并让类型系统确定当它d addMonth 5 出现在您的代码中时必须应用它。

编辑

您通过编写创建的别名似乎type PrimitiveDate = Long不是由 scala 编译器强制执行的。是创建一个适当的类,包含 Long,这是在 Scala 中创建强制类型的唯一方法吗?

您认为能够为原始类型创建强制类型别名有多大用处?

4

3 回答 3

12

好吧,逃逸分析应该意味着最新的 JVM 实际上不必创建丰富的包装器来调用该addMonth方法。

在实践中实际发生的程度显然取决于JVM 决定这些方法在对象创建中添加了多少运行时热点。当逃逸分析没有发生时,显然 JVM 将不得不“装箱”(如你所说)Long在包装类的新实例中。它不涉及“类创建”——它将涉及“创建类的实例”。这个实例是短暂的,然后会立即被 GC-d,所以开销(虽然很小)是:

  • 实例的内存分配
  • GC-ing 实例

如果您确实在编写非常低延迟的代码并且您试图最小化垃圾创建(在一个紧密的循环中),那么这些显然只会成为任何类型的问题。只有你知道是不是这样。

至于该方法是否适合您(逃逸分析会帮助您),您必须在野外进行测试。众所周知,为这类事情编写微基准测试非常困难。


我不太喜欢将这些类型别名作为公共 API 的一部分的原因是 scala 并没有像我希望的那样严格执行它们。例如:

type PrimitiveDate = Long
type PrimitiveSpeed = Long
type Car = String
type Meeting = String

var maxSpeeds : Map[Car, PrimitiveSpeed] = Map.empty

//OOPS - much too easy to accidentally send the wrong type
def setDate(meeting : Meeting, date : PrimitiveDate) = maxSpeeds += (meeting -> date)
于 2010-10-20T10:15:26.070 回答
4

在给定的示例中,您实际上并没有创建新类型,它只是预先存在的 Long 类型的别名。

这是我经常使用的一种技术来处理笨拙的嵌套连接。例如,我会使用别名type Grid = Seq[Seq[Int]]以避免必须Seq[Seq[Int]]一遍又一遍地为各种参数指定。

您可以很高兴地将 a 传递Long给采用方法的PrimitiveDate方法,尽管您确实具有代码更好地自我记录的优势。

如果你真的想创建一个具有强制类型安全和方便模式匹配的新类型,我会使用一个案例类:

case class PrimitiveDate(value:Long)

并且,为了方便,甚至可能提供隐式 Long=>PrimitiveDate 转换。

于 2010-10-20T12:59:46.153 回答
4

在您提出这个问题 11 个月后,Miles Sabin 发现了一种在 Scala中创建未装箱新类型的非常简单、优雅且高效的方法。与类型别名不同,类型标签是强制执行的。基元类型需要最少的样板文件(每个基元一行)来提供专业化。

一年后,他在Shapeless中添加了一个更完善、更强大的版本。如果您不想要该优秀库的其余部分,则该概念非常简单明了,可以在不添加 Shapeless 的项目中复制。

当然,您和回答您问题的人可能都知道这一点,但值得在这里补充,因为这仍然是一个重要的问题。

于 2015-08-24T16:00:52.410 回答