Неявные параметры с двумя экземплярами одного типа

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

Пример взят из

Функциональное моделирование реактивной области

книга.


person Alexandr    schedule 05.04.2018    source источник
comment
Совершенно нормально опубликовать этот ответ как фактический ответ на ваш собственный вопрос, который сделает его более читаемым и видимым для других.   -  person slouc    schedule 05.04.2018


Ответы (1)