具有 2 个相同类型实例的隐式参数

Implicit parameters with 2 instances of the same type

Scala 允许我们定义隐式参数。根据确切的类型选择正确的定义。在下面的示例中,为相同的 Money 类型定义了 2 个 monoid 实例,MoneyAdditionMonoid 用于累加,MoneyCompareMonoid 用于比较。

有什么不清楚的,Scala 编译器如何理解 maxDebitOnDaysumBalances 函数中使用了哪个幺半群?你能不能给我强调一下,我看不懂。

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

示例来自

Functional Reactive-Domain-Modeling

本书。

答案在本书的论坛中找到:Explicit dictionary passing technique in listing 4.3

我们必须显式传递隐式参数,因此:

ltx 是交易列表

maxDebitOnday(ltx)(Monoid.MoneyCompareMonoid) 

lbs 是余额列表

sumBalances(lbs)(Monoid.MoneyAdditionMonoid)