0

Scala allows us to define implicit parameters. Based on the exact type the correct definition is chosen. In example below, 2 monoid instances are defined for the same Money type, MoneyAdditionMonoid for accumulation and MoneyCompareMonoid for comparison.

What is not clear, how Scala compiler undestands which monoid is used in the maxDebitOnDay and sumBalances functions? Can you please highlight this to me, I cannot understand it.

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

The example is from

Functional Reactive-Domain-Modeling

book.

Alexandr
  • 9,213
  • 12
  • 62
  • 102
  • It's perfectly fine to post this answer as an actual answer to your own question, makes it more readable and visible for others. – slouc Apr 05 '18 at 15:02

1 Answers1

0

The answer is found in the forum of the book: Explicit dictionary passing technique in listing 4.3

We must pass explicitly the implicit argument so:

ltx being a list of Transactions

maxDebitOnday(ltx)(Monoid.MoneyCompareMonoid) 

lbs being a list of Balances

sumBalances(lbs)(Monoid.MoneyAdditionMonoid) 
Alexandr
  • 9,213
  • 12
  • 62
  • 102