使用 Scalaz(或 Shapeless)为每个子类创建 Monoids
Creating Monoids for every subclass using Scalaz (or Shapeless)
是否可以为每个子class创建幺半群?例如,
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
}
我想添加更多代表 货币 的大小写 classes(例如 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
因此,我必须为新案例 classes 实现幺半群。这有点样板。
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))
}
小建议
首先,我想建议从案例 classes 中删除 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
无形实现
从这里您可以根据需要通过 shapeless
构建简单的实现:
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
现在不是 class 的问题,它只是编译时类型标签
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
功能轻松派生类型 classes:
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) //
是否可以为每个子class创建幺半群?例如,
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
}
我想添加更多代表 货币 的大小写 classes(例如 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
因此,我必须为新案例 classes 实现幺半群。这有点样板。
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))
}
小建议
首先,我想建议从案例 classes 中删除 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
无形实现
从这里您可以根据需要通过 shapeless
构建简单的实现:
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
现在不是 class 的问题,它只是编译时类型标签
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
功能轻松派生类型 classes:
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) //