scalaz.std.MapInstances
声明任何值为Semigroup
的地图本身就是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
?
答案 0 :(得分:18)
<Grid/>
是type class,这意味着在代表您的数据的类中扩展它不是预期用途。
如果您熟悉Java,请考虑Comparable
和Comparator
之间的区别。如果您使用Java实现Semigroup
并且希望支持比较Num
值,则可以使用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
个实例加在一起。
使用此模式而不是继承具有类似于Num
在Java中优于Comparator
的优点的优点:您可以将数据类型定义与您可能希望的所有操作的定义分开执行该数据,您可以拥有多个实例,等等。通过允许您将类型类的实例放入隐式范围,Scala只需要进一步利用这些优势,这样您就不必手动传递它们(如果你需要或想要,你仍然可以这样做。
答案 1 :(得分:1)
如果你看一下scalaz中的半群,monoid或任何其他类型类的其他实例,你会发现它们不使用继承。
事实上,类型类作为一个整体是继承的一种替代方法,因为你可以在类上赋予行为而不必担心类层次结构。
您可以通过为特定类型声明某个类型类的实例来执行此操作:
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中使类型类可用的机制是通过隐式参数。
| + |运算符隐式地请求一个正确类型的半群 - 如果找不到它,它就不会编译。
你没有声明一个Semigroup的隐式实例,所以这就是调用| + |的原因不会起作用。