Scala позволяет нам определять неявные параметры. Основываясь на точном типе, выбирается правильное определение. В приведенном ниже примере 2 экземпляра моноида определены для одного и того же типа Money
, MoneyAdditionMonoid
для накопления и MoneyCompareMonoid
для сравнения.
Что непонятно, как компилятор Scala понимает, какой моноид используется в функциях maxDebitOnDay
и sumBalances
? Не могли бы вы выделить это мне, я не могу этого понять.
trait Monoid[T] {
def zero: T
def op(t1: T, t2: T): T
}
object Monoid {
def apply[T](implicit monoid: Monoid[T]) = monoid
implicit val IntAdditionMonoid = new Monoid[Int] {
val zero = 0
def op(i: Int, j: Int) = i + j
}
implicit val BigDecimalAdditionMonoid = new Monoid[BigDecimal] {
val zero = BigDecimal(0)
def op(i: BigDecimal, j: BigDecimal) = i + j
}
implicit def MapMonoid[K, V: Monoid] = new Monoid[Map[K, V]] {
def zero = Map.empty[K, V]
def op(m1: Map[K, V], m2: Map[K, V]) = m2.foldLeft(m1) { (a, e) =>
val (key, value) = e
a.get(key).map(v => a + ((key, implicitly[Monoid[V]].op(v, value)))).getOrElse(a + ((key, value)))
}
}
final val zeroMoney: Money = Money(Monoid[Map[Currency, BigDecimal]].zero)
implicit def MoneyAdditionMonoid = new Monoid[Money] {
val m = implicitly[Monoid[Map[Currency, BigDecimal]]]
def zero = zeroMoney
def op(m1: Money, m2: Money) = Money(m.op(m1.m, m2.m))
}
object MoneyOrdering extends Ordering[Money] {
def compare(a:Money, b:Money) = a.toBaseCurrency compare b.toBaseCurrency
}
import MoneyOrdering._
import scala.math.Ordering
implicit val MoneyCompareMonoid = new Monoid[Money] {
def zero = zeroMoney
def op(m1: Money, m2: Money) = if (gt(m1, m2)) m1 else m2
}
}
package monoids.monoid
import java.util.Date
sealed trait TransactionType
case object DR extends TransactionType
case object CR extends TransactionType
sealed trait Currency
case object USD extends Currency
case object JPY extends Currency
case object AUD extends Currency
case object INR extends Currency
object common {
type Amount = BigDecimal
}
import common._
case class Money(m: Map[Currency, Amount]) {
def toBaseCurrency: Amount = ???
}
trait Analytics[Transaction, Balance, Money] {
def maxDebitOnDay(txns: List[Transaction])(implicit m: Monoid[Money]): Money
def sumBalances(bs: List[Balance])(implicit m: Monoid[Money]): Money
}
case class Transaction(txid: String, accountNo: String, date: Date, amount: Money, txnType: TransactionType, status: Boolean)
case class Balance(b: Money)
object Analytics extends Analytics[Transaction, Balance, Money] {
import Monoid._
final val baseCurrency = USD
private def valueOf(txn: Transaction): Money = {
if (txn.status) txn.amount
else MoneyAdditionMonoid.op(txn.amount, Money(Map(baseCurrency -> BigDecimal(100))))
}
private def creditBalance(bal: Balance): Money = {
if (bal.b.toBaseCurrency > 0) bal.b else zeroMoney
}
def maxDebitOnDay(txns: List[Transaction])(implicit m: Monoid[Money]): Money = {
txns.filter(_.txnType == DR).foldLeft(m.zero) { (a, txn) => m.op(a, valueOf(txn)) }
}
def sumBalances(bs: List[Balance])(implicit m: Monoid[Money]): Money =
bs.foldLeft(m.zero) { (a, bal) => m.op(a, creditBalance(bal)) }
}
Пример взят из
Функциональное моделирование реактивной области
книга.