我正在尝试路径依赖类型。在我的简单示例中,我使用Currency对象来确保Money计算只能在相同货币的Money上执行。
// Simple currency class
case class Currency(code: String, name: String, symbol: String) {
// Money amounts in this currency
// Operations are only supported on money of the same currency
case class Money(amount: BigDecimal) {
override def toString: String = s"$code $amount"
val currency: Currency.this.type = Currency.this
def +(rhs: Money): Money = Money(amount + rhs.amount)
def -(rhs: Money): Money = Money(amount - rhs.amount)
}
}
在repl中使用上述类简单计算是直截了当的。
val e1 = Euro.Money(5)
val e2 = Euro.Money(9)
e1 + e2 // Compiles fine
val d1 = Dollar.Money(6)
d1 + e2 // Doesn't compile as expected
这些很简单,因为编译器可以很容易地证明e1和e2共享一种通用货币。然而,当我从文件或数据库中读取金额列表时,证明货币实例共享共同货币要困难得多。例如,我无法看到如何实现下面的整理功能。
trait CurrencyAndMonies {
val currency: Currency
val monies: List[currency.Money]
}
// Take a list of money in different currencies and group them by currency
// so their shared Currency type is available to static type checking
// in further calculations
def collate(Seq[Currency#Money]): List[CurrencyAndMonies] = ???
是否可以根据货币对货币进行分类并重新建立它们之间的联系?如果是这样怎么样?我不介意更改签名或从数据库中读取Money金额的方式。
答案 0 :(得分:1)
你可以垂头丧气:
new CurrencyAndMonies {
val currency = foo
val monies = bars.map(_.asInstanceOf[currency.Money])
}
按Money#currency
分组。
向下转换不是运行时检查的,因此您必须确保货币值具有正确的货币(您已通过货币分组),但它将进行编译。
答案 1 :(得分:1)
在您的示例中键入签名
def collate(Seq[Currency#Money]): List[CurrencyAndMonies]
并不要求所有的钱都来自同一种货币,它可以是来自任何货币的随机货币。
val Euro = Currency("EUR", "Euro", "EUR")
val USD = Currency("USD", "Dollar", "$")
def collateOld(s: List[Currency#Money]): CurrencyAndMonies = ???
// Compiles successfully -> ERROR
collateOld(List(USD.Money(10), Euro.Money(20)))
通常,您必须传递货币实例和货币清单。例如,您可以这样做:
abstract class CurrencyAndMonies(val currency: Currency) {
type Money
def monies: List[Money]
}
def collate(c: Currency)
(m: List[c.Money]): CurrencyAndMonies { type Money = c.Money } =
new CurrencyAndMonies(c) {
type Money = c.Money
val monies = m
}
collate(Euro)(List(Euro.Money(10), Euro.Money(20)))
奇怪的是,它需要在CurrencyAndMonies中重新定义类型Money,但我无法理解为什么只是货币。货币不起作用。如果您将构造函数设为私有并且仅整理一种创建类实例的方法,那么您应该使用保证类型安全的方法