是否有可能为每个子类创建幺半群?例如,
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 }
我想添加更多代表货币的案例类(例如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
因此,我必须为新案例类实现monoids.它有点样板.
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)) }
解决方法
小建议
首先,我想建议从案例类中删除Monoid需求,因为它们将在每个Currency实例中携带隐式值.如果没有这个要求,你的包装器可以更高效,甚至可以实现为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
无形的实施
从这里,您可以根据需要通过无形构建简单的实现:
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) } }
并验证它
import scalaz.Syntax.monoid._ import scalaz.std.anyVal._ println(2.USD |+| 3.USD) // USD(5)
进一步改进
你可以摆脱没有形状.考虑这样的实现:
trait CurrencyUnit{ def show(amounts: String) = s"$amounts $this" } final case class Currency[A,U <: CurrencyUnit](amount: A) extends AnyVal
CurrencyUnit现在不是类的问题,它只是编译时类型标记
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) }
您可以根据需要进行配置
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) }
最重要的是通过scalaz.Isomorphism.Iso功能很容易派生类型类:
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 }
最后,您也可以验证此解决方案
import scalaz.Syntax.monoid._ import scalaz.std.anyVal._ println((2.USD |+| 3.USD).shows) // $5
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。