在Scala中通用构造函数

4

我最近在学习Scala。我想尝试使用新添加的AnyVal特性,推导出一种创建不兼容值类型的方法,这些类型不能被意外地赋值给彼此。

我能想到的最好的方式大概是这样的:

object Measurements {
  trait ValueType[T] extends Any {
    def value: T
  }

  trait Measurement[A <: ValueType[Double]] extends Any {
    def modify(fn: (Double, A) => Double, value: A): A
    def +(mod: A) = modify((x: Double, y: A) => x + y.value, mod)
    def -(mod: A) = modify((x: Double, y: A) => x - y.value, mod)
    def *(mod: A) = modify((x: Double, y: A) => x * y.value, mod)
    def /(mod: A) = modify((x: Double, y: A) => x / y.value, mod)
  }

  case class Frequency(value: Double) extends AnyVal 
      with ValueType[Double] 
      with Measurement[Frequency] 
  {
    def modify(fn: (Double, Frequency) => Double, mod: Frequency) 
      = Frequency(fn(value, mod))
  }

  case class Amplitude(value: Double) extends AnyVal 
      with ValueType[Double] 
      with Measurement[Amplitude] 
  {
    def modify(fn: (Double, Amplitude) => Double, mod: Amplitude) 
      = Amplitude(fn(value, mod))
  }

  case class Wavelength(value: Double) extends AnyVal 
      with ValueType[Double] 
      with Measurement[Wavelength] 
  {
    def modify(fn: (Double, Wavelength) => Double, mod: Wavelength) 
      = Wavelength(fn(value, mod))
  }
}
import Measurements._
Frequency(150) + Frequency(10) // ==> Frequency(160)
Amplitude(23.2) * Amplitude(2) // ==> Amplitude(46.4)
Amplitude(50) + Frequency(50)  // ==> Compile-time Type Error

很不幸,我需要为每个实例定义唯一的modify函数,因为无法使用通用类型A来定义类似于A(value)的东西。没有办法定义构造函数限制。否则,我可以在特性上定义一些共同的东西,例如:

def modify(fn: (Double, A) => Double, mod: A) = A(fn(value, mod))

我已经尝试在泛型变量上调用apply(Double),但无法访问。我还尝试过创建一个工厂来简化事情,但是我无法想出比我现在所做的更为优雅的解决方案。我在C#中总是遇到相同的问题。
是否有一种方法可以将依赖于不同(但相关)类的共同构造函数类型的代码因子化?

通过使用类型类,您的程序可以在不进行反射的情况下得到改进。请查看丹尼尔·韦斯特海德(Daniel Westheide)在他杰出的《Scala新手指南》中关于此主题的文章。虽然这可能与当前无关,但请查看Squants,它是一个“数据类型框架和领域特定语言(DSL),用于表示量、其度量单位及其维度关系。” - Michael Ahlers
1个回答

4
我认为如果不使用运行时反射(或者可能是宏),这是不可能的。基本上有三个问题,其中你已经指出了两个:
  • 接口无法声明强制构造函数签名。

  • 无法在 A 上调用诸如 apply 的方法,因为它是一个类型变量,而不是类或对象。

  • 由于值类(目前)只能扩展 通用特质,而不能扩展抽象类,因此无法使用 TypeTags 以获取当 Measurement 被扩展时实例化 A 所属的类。

我能想到的最好方法是以下方法。请注意,它使用反射,并且如果 Measurement 的具体实例未声明适当的构造函数,则可能会在运行时引发异常。

// ... as above ...

trait Measurement[A <: ValueType[Double]] extends Any { self: A =>
  def modify(fn: (Double, A) => Double, mod: A): A =
    this.getClass
        .getConstructor(this.getClass)
        .newInstance(fn(value, mod): java.lang.Double)

  // ... as above ...
}

case class Frequency(value: Double)
    extends AnyVal 
       with ValueType[Double] 
       with Measurement[Frequency]

// ... etc ...

Frequency(150) + Frequency(10) // ==> Frequency(160)
Amplitude(23.2) * Amplitude(2) // ==> Amplitude(46.4)
Amplitude(50) + Frequency(50)  // ==> Compile-time Type Error

自身类型注释self: A确保在特质Measurement内部可以访问由ValueType声明的value字段。

我有点担心这种情况(甚至需要两天才有人尝试回答)。反射解决方案确实可行,但通常会牺牲性能和安全性,这很遗憾。不过,作为参考还是很好的。+1并接受答案。 - KChaloux

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接