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.