2014-12-31 1 views
1

Я экспериментирую с зависимыми от пути типами. В моем простом примере я использую объект Currency, чтобы гарантировать, что денежные расчеты могут быть выполнены только на деньги той же валюты.Восстановить связь между зависимым от пути типом и его родителем во время выполнения?

// 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) 
    } 
} 

Использование вышеуказанного класса простых калорий в реплике осуществляется вперед.

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] = ??? 

Возможно ли отсортировать денежные средства на основе валюты и восстановить связь между ними? И если да, то как? Я не против изменения подписи или способа чтения денег из базы данных.

ответ

1

Вы можете опущенными:

new CurrencyAndMonies { 
    val currency = foo 
    val monies = bars.map(_.asInstanceOf[currency.Money]) 
} 

Группа по Money#currency.

Убитый не проверен во время выполнения, поэтому вам нужно убедиться, что денежная стоимость имеет правильную валюту (которую вы уже делаете, группируя валюту), но она будет скомпилирована.

+0

Спасибо за ваш ответ. Я думаю, вы правы, что мне придется бросить его, что очень жаль. Я смотрю на самый опрятный способ его литья в данный момент (возможно, поместив его за образец, соответствующий безопасности). – iain

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, но я не могу понять, почему только currency.Money не работает. Если вы сделаете конструктор приватным и сопоставьте только один способ создания экземпляра класса, вы должны быть хорошо использовать с гарантированным типом безопасности.

+0

Спасибо за ответ. Я хотел иметь возможность сопоставлять деньги с несколькими различными валютами. Потому что, когда вы читаете их из файла, время компиляции между экземпляром Money и его валютой нарушается. Я хочу восстановить эту ссылку, чтобы мои операции снова стали безопасными. – iain

+0

aaah, понял, я неправильно понял оригинальный вопрос –