幺半群如何泛化类型

How monoids generalize over types

当我看到以一种自然的方式为元组扩展了幺半群操作时,我正在 scala 中玩猫的幺半群:

import cats.Monoid

object mon {
  implicit object IntMonoid extends Monoid[Int] {
    def combine(a: Int, b: Int) = a*a + b*b
    def empty = 0
  }

  implicit object ListMonoid extends Monoid[List[Int]] {
    def combine(a: List[Int], b: List[Int]): List[Int] =
      a.zip(b).map(z => z._1 * z._2)
    def empty = List(1)
  }

  def comb[T](a: T, b: T)(implicit m: Monoid[T]) =
    m.combine(a, b)
}

val list1 = List(1, 2, 3)
val list2 = List(2, 3, 4)
println(mon.comb(list1, list2)) // outputs: List(2, 6, 12) as expected

val int1 = 2
val int2 = 4
println(mon.comb(int1, int2)) // outputs: 20 as expected

val x = (list1, int1)
val y = (list2, int2)
println(mon.comb(x, y)) // outputs: (List(2, 6, 12),20)

最后的输出应该是 'natural' 的方式,但是 de 编译器是怎么知道怎么做的呢? 我一直试图在 Cats 的源代码中寻找它,但我对 Scala 的经验还不够丰富,无法知道要寻找什么。我想相同的方法适用于像半群这样的类似结构。

您的问题归结为泛型的类型类的隐式派生是如何工作的;让我们看两个例子:

A case where we want to provide an instance no matter what the generic is:

// Similar to the code you had, but without being tied to just List[Int],
// Since in this case the Int part is irrelevant.
implicit def monoidList[A]: Monoid[List[A]] =
  new Monoid[List[A]] {
    override final val empty: List[A] = Nil

    override final def combine(l1: List[A], l2: List[A]): List[A] =
      l1 ::: l2
  }

A case where we require a proof of the generic type to provide the instance of the complex type:

implicit def optionMonoid[A](implicit aMonoid: Monoid[A]): Monoid[Option[A]] =
  new Monoid[Option[A]] {
    override final val empty: Option[A] = None

    override final def combine(o1: Option[A], o2: Option[A]): Option[A] =
      (o1, o2) match {
        case (None, None)         => None
        case (Some(a), None)      => Some(a)
        case (None, Some(a))      => Some(a)
        case (Some(a1), Some(a1)) => Some(aMonoid.combine(a1, a2))
      }
  }

因此,您现在可以想象 catsMonoid[Tuple2[A, B]] 是如何工作的,但为了完整起见,代码应该是这样的:

implicit def tuple2Monoid[A, B](implicit aMonoid: Monoid[A], bMonoid: Monoid[B]): Monoid[(A, B)] =
  new Monoid[(A, B)] {
    override final def empty: (A, B) =
      (aMonoid.empty, bMonoid.empty)

    override final def combine(t1: (A, B), t2: (A, B)): (A, B) =
      (t1, t2) match {
        case ((a1, b1), (a2, b2)) => (aMonoid.combine(a1, a2), bMonoid.combine(b1, b2))
      }
  }