Is it possible to create monoids for every sub-class? For example,
package currency
final case class GBP[A: Monoid](amount: A)
object Implicits {
implicit class CurrencyOps[A: Monoid](a: A) {
def GBP = currency.GBP(a)
}
implicit def gbpMonoid[A: Monoid]: Monoid[GBP[A]] = new Monoid[GBP[A]] {
override def zero =
GBP(Monoid[A].zero)
override def append(f1: GBP[A], f2: => GBP[A]): GBP[A] =
GBP(Semigroup[A].append(f1.amount, f2.amount))
}
}
test("GBP support plus") {
1.GBP |+| 2.GBP shouldBe 3.GBP // passed
}
I would like to add more case classes representing currency (e.g USD
, EUR
, ..)
sealed trait Currency
final case class GBP[A: Monoid](amount: A) extends Currency
final case class USD[A: Monoid](amount: A) extends Currency
final case class EUR[A: Monoid](amount: A) extends Currency
As a result, I have to implement monoids for new case classes. It's somewhat boilerplate.
implicit class CurrencyOps[A: Monoid](a: A) {
def GBP = currency.GBP(a)
def EUR = currency.EUR(a)
def USD = currency.USD(a)
}
implicit def gbpMonoid[A: Monoid]: Monoid[GBP[A]] = new Monoid[GBP[A]] {
override def zero =
GBP(Monoid[A].zero)
override def append(f1: GBP[A], f2: => GBP[A]): GBP[A] =
GBP(Semigroup[A].append(f1.amount, f2.amount))
}
implicit def usdMonoid[A: Monoid]: Monoid[USD[A]] = new Monoid[USD[A]] {
override def zero =
USD(Monoid[A].zero)
override def append(f1: USD[A], f2: => USD[A]): USD[A] =
USD(Semigroup[A].append(f1.amount, f2.amount))
}
implicit def eurMonoid[A: Monoid]: Monoid[EUR[A]] = new Monoid[EUR[A]] {
override def zero =
EUR(Monoid[A].zero)
override def append(f1: EUR[A], f2: => EUR[A]): EUR[A] =
EUR(Semigroup[A].append(f1.amount, f2.amount))
}
First I'd like to propose remove Monoid
requirement from case classes, as they will carry implicit value in each Currency
instance. Without this requirement your wrappers could be much more efficient and even implemented as value classes:
sealed trait Currency extends Any
final case class GBP[A](amount: A) extends AnyVal with Currency
final case class USD[A](amount: A) extends AnyVal with Currency
final case class EUR[A](amount: A) extends AnyVal with Currency
From here you can build simple implementation via shapeless
as required:
import scalaz._
import shapeless._
implicit def monoidCurrency[A, C[_] <: Currency]
(implicit monoid: Monoid[A], gen: Generic.Aux[C[A], A :: HNil]) =
new Monoid[C[A]] {
def zero: C[A] = gen.from(monoid.zero :: HNil)
def append(f1: C[A], f2: => C[A]): C[A] = {
val x = gen.to(f1).head
val y = gen.to(f2).head
gen.from(monoid.append(x, y) :: HNil)
}
}
and verify it
import scalaz.syntax.monoid._
import scalaz.std.anyVal._
println(2.USD |+| 3.USD) // USD(5)
You can get rid of shapeless at all. Consider such implementations:
trait CurrencyUnit{
def show(amounts: String) = s"$amounts $this"
}
final case class Currency[A, U <: CurrencyUnit](amount: A) extends AnyVal
CurrencyUnit
now is not a matter of class, it's just compile-time type-tag
implicit case object GBP extends CurrencyUnit
implicit case object USD extends CurrencyUnit{
override def show(amounts: String) = s"$$$amounts "
}
implicit case object EUR extends CurrencyUnit
implicit class CurrencyOps[A](a: A) {
def GBP = Currency[A, GBP.type](a)
def EUR = Currency[A, EUR.type](a)
def USD = Currency[A, USD.type](a)
}
which you can configure for your needs
import scalaz.syntax.show._
implicit def currencyShow[A: Show, U <: CurrencyUnit](implicit unit: U) =
new Show[Currency[A, U]] {
override def shows(f: Currency[A, U]) = unit.show(f.amount.shows)
}
And most important easily derive typeclasses via scalaz.Isomorphism.Iso
functionality:
import Isomorphism._
implicit def currencyIso[A, U <: CurrencyUnit] = new (Currency[A, U] <=> A) {
def to: (Currency[A, U]) => A = _.amount
def from: (A) => Currency[A, U] = Currency[A, U]
}
implicit def currencyMonoid[A: Monoid, U <: CurrencyUnit] =
new IsomorphismMonoid[Currency[A, U], A] {
def G: Monoid[A] = implicitly
def iso: Currency[A, U] <=> A = implicitly
}
Finally you can verify this solution too
import scalaz.syntax.monoid._
import scalaz.std.anyVal._
println((2.USD |+| 3.USD).shows) // $5
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With