Writerモナドの使い道 [自作モノイド]

case class Product(money: Int)

case class Coins(gohyakuen: Int, hyakuen: Int, gojuen: Int, juen: Int) {
  def +(other: Coins) = Coins(gohyakuen + other.gohyakuen, hyakuen + other.hyakuen, gojuen + other.gojuen, juen + other.juen)
  def sum(): Int = gohyakuen * 500 + hyakuen * 100 + gojuen * 50 + juen * 10
}

implicit val monoidCoins = new Monoid[Coins] {
  override def empty: Coins = Coins(0, 0, 0, 0)
  override def combine(x: Coins, y: Coins): Coins = x + y
}

def buyProduct(coins: Coins): Writer[Coins, Product] =
  for {
    _ <- Writer.tell(coins)
    r <- Writer.value[Coins, Product](Product(coins.sum()))
  } yield r

def buyProducts(coinsList: List[Coins]): Writer[Coins, List[Product]] =
  coinsList.map(buyProduct).sequence

// 500円玉3枚、100円玉3枚、50円玉5枚、10円玉6枚使用
// 230円と710円と1170円の品をかう
// (Coins(3,3,5,6),List(Product(230), Product(710), Product(1170)))
println(buyProducts(List(Coins(0, 1, 2, 3), Coins(1, 2, 0, 1), Coins(2, 0, 3, 2))).run)