在 Scalaz 中使用自定义半群 class 时遇到问题

trouble using custom semigroup class with Scalaz

scalaz.std.MapInstances 声明任何值为 Semigroup 的映射本身就是 Monoid。由于 IntSemigroup,因此以下代码有效:

def merge[K](maps : Iterator[Map[K, Int]]) : Map[K, Int] = maps.reduce(_ |+| _)

但是,令我惊讶的是以下代码不起作用:

class Num(value : Int) extends Semigroup[Num] {
    def append(x : Num, y : Num): Num = new Num(x.value + y.value)
}

def merge[K](maps : Iterator[Map[K, Num]]) : Map[K, Num] = maps.reduce(_ |+| _)

任何人都可以向我解释为什么值是我自定义 Semigroup class 的地图不被视为 Monoid 吗?

Semigrouptype class,这意味着在代表您的数据的 class 中扩展它不是预期用途。

如果您熟悉 Java,请想想 Comparable and Comparator 之间的区别。如果您在 Java 中实现 Num 并且希望支持比较 Num 值,您可以让 Num class 实现 Comparable[Num] ,或者您可以提供一个 Comparator[Num] 类型的值来描述如何比较两个 Num 实例。

Semigroup 类似于 Comparator,而不是 Comparable——您不扩展它,您提供一个值来描述如何附加您的类型的实例。请注意,在您的版本中,实例的 value 参数未用于 append:

的实现
import scalaz.Semigroup

class Num(value: Int) extends Semigroup[Num] {
  def append(x: Num, y: Num): Num = new Num(x.value + y.value)
}

你应该这样写:

import scalaz.Semigroup

class Num(val value: Int)

object Num {
  implicit val numSemigroup: Semigroup[Num] =
    Semigroup.instance((a, b) => new Num(a.value + b.value))
}

然后:

scala> def merge[K](maps: List[Map[K, Num]]): Map[K, Num] = maps.reduce(_ |+| _)
merge: [K](maps: List[Map[K,Num]])Map[K,Num]

scala> val merged = merge(List(Map("a" -> new Num(1)), Map("a" -> new Num(2))))
merged: Map[String,Num] = Map(foo -> Num@51fea105)

scala> merged("a").value
res5: Int = 3

通过在 Num 的伴生对象中放置类型 Semigroup[Num] 的隐式值,我们说这是我们希望在需要加在一起的任何时候使用的操作Num 个实例。

使用此模式而不是继承的优点类似于 Comparator 在 Java 中优于 Comparable 的优点:您可以将数据类型定义与所有的定义分开您可能想对该数据执行的操作,您可以有多个实例,等等。Scala 通过允许您将类型 class 的实例放入隐式范围,从而进一步利用这些优势,这样您就不会不必手动传递它们(尽管如果需要或想要,您仍然可以这样做)。

如果您在 scalaz 中查看半群、幺半群或任何其他类型的其他实例class,您会发现它们不使用继承。

事实上,typeclasses 作为一个整体是继承的一种替代方案,因为您可以将行为赋予 classes 而不必担心 class 层次结构。

您可以通过为特定类型声明某种类型的实例class来做到这一点:

implicit val numMonoidInstance = new Monoid[Num]{
  override def zero = new Num(0)
  override def append(n1:Num, n2:Num) = new Num(n1.value + n2.value)
}

scala 中使类型classes 可用的机制是通过隐式参数。

|+|运算符隐式要求正确类型的 Semigroup - 如果找不到,则不会编译。

您没有声明 Semigroup 的隐式实例,所以这就是调用 |+| 的原因是行不通的。