scalaz.std.MapInstances
声明任何值为a的地图Semigroup
本身就是a Monoid
.由于Int
是Semigroup
,下面的代码工作:
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
类的地图不被视为Monoid
s?
Semigroup
是一个类型类,这意味着在表示数据的类中扩展它不是预期的用法.
如果你熟悉Java,认为之间的差异Comparable
和Comparator
.如果您Num
在Java 中实现并希望支持比较Num
值,则可以使用Num
类实现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
通过Semigroup[Num]
在Num
同伴对象中放入一个隐式的type值,我们说这是我们想要在需要将Num
实例添加到一起时使用的操作.
使用这种模式,而不是继承具有类似于的优点在于优势Comparator
拥有Comparable
在Java中:您可以将数据类型定义所有你可能需要对数据进行操作的定义分开,你可以有多个实例,等等. Scala通过允许您将类型类的实例放入隐式作用域中来进一步利用这些优势,这样您就不必手动传递它们(尽管您仍然可以在需要或想要时执行此操作).