"composing" 使用猫的幺半群的惯用方法?

Idiomatic approach to "composing" monoids using cats?

我想 "compose" 两个使用猫的幺半群。如果存在已定义的 Monoid[(A, A) => Int],那么我希望能够使用 Monoid[(A, A) => Int]combineempty 方法创建 Monoid[Preference[A]]。我在这里松散地使用术语 "composing" 因为我不确定我想要做的转换是否准确地称为合成。

这是我目前的尝试...

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)
      }
    }
  }
}

...这可以编译,但我想知道是否有使用猫的更惯用的解决方案。

似乎应该可以以某种方式将 Monoid[(A,A) => Int] 与需要 f:(A, A) => Int 和 returns 和 Preference[A]from 组合器组合起来Monoid[Preference[A]] 但我不知道该怎么做。

我看过这个 SO post,它讨论了使用 product 组合器组合幺半群,这不是我想要的。

我不知道 cats 中有什么直接内置的。

看来您在 Preference[A](A, A) => Int 之间存在同构,您只是想将幺半群结构从 (A, A) => Int 转移到 Preference[A]。这可以通用地表示为任意类型 AB:

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)
}

使用这个辅助方法,您在 Preference 中的 monoid 就变成了:

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)
  )

完整的可编译示例(没有任何依赖项):

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)
  )
}