I would like to "compose" two monoids using cats. If there exists a defined Monoid[(A, A) => Int]
, then I would like to be able to create a Monoid[Preference[A]]
using the combine
and empty
methods of the Monoid[(A, A) => Int]
. I am using the term "composing" loosely here because I am not sure that the transform I want to do is accurately called composition.
Here is my current attempt...
import cats._
import cats.implicits._
trait Preference[A] extends Order[A]
object Preference {
def from[A](f: (A, A) => Int): Preference[A] = {
new Preference[A] {
def compare(a1: A, a2: A): Int = {
f(a1, a2)
}
}
}
def monoid[A](implicit ev: Monoid[(A, A) => Int]): Monoid[Preference[A]] = {
new Monoid[Preference[A]] {
def combine(p1: Preference[A], p2: Preference[A]): Preference[A] = {
new Preference[A] {
def compare(a1: A, a2:A): Int = {
ev.combine(p1.compare, p2.compare)(a1, a2)
}
}
}
def empty: Preference[A] = {
from(ev.empty)
}
}
}
}
...this compiles but I would like to know if there is a more idiomatic solution available using cats.
Seems like it should be possible to somehow compose the Monoid[(A,A) => Int]
with the from
combinator that takes a f:(A, A) => Int
and returns a Preference[A]
to create a Monoid[Preference[A]]
but I can not figure out how to do it.
I have seen this SO post which discusses composing monoids using a product
combinator which is not what I want.
I'm not aware of anything built-in into cats
directly.
It seems that you have an isomorphism between Preference[A]
and (A, A) => Int
, and you simply want to transfer the monoid-structure from (A, A) => Int
to Preference[A]
. This can be expressed generically for arbitrary types A
and B
:
def fromIsomorphicMonoid[A, B](
forward: A => B,
inverse: B => A
)(implicit aMon: Monoid[A]): Monoid[B] = new Monoid[B] {
def combine(b1: B, b2: B): B =
forward(aMon.combine(inverse(b1), inverse(b2)))
def empty: B = forward(aMon.empty)
}
With this helper method, your monoid
in Preference
becomes just:
def monoid[A](implicit ev: Monoid[(A, A) => Int]): Monoid[Preference[A]] =
fromIsomorphicMonoid(
from,
(p: Preference[A]) => (x:A, y:A) => p.compare(x, y)
)
Full compilable example (without any dependencies):
trait Monoid[X] {
def empty: X
def combine(x: X, y: X): X
}
trait Order[A] {
def compare(a1: A, a2: A): Int
}
def fromIsomorphicMonoid[A, B](
forward: A => B,
inverse: B => A
)(implicit aMon: Monoid[A]): Monoid[B] = new Monoid[B] {
def combine(b1: B, b2: B): B =
forward(aMon.combine(inverse(b1), inverse(b2)))
def empty: B = forward(aMon.empty)
}
trait Preference[A] extends Order[A]
object Preference {
def from[A](f: (A, A) => Int): Preference[A] = {
new Preference[A] {
def compare(a1: A, a2: A): Int = {
f(a1, a2)
}
}
}
def monoid[A](implicit ev: Monoid[(A, A) => Int])
: Monoid[Preference[A]] = fromIsomorphicMonoid(
from,
(p: Preference[A]) => (x:A, y:A) => p.compare(x, y)
)
}