简体   繁体   中英

trouble using custom semigroup class with Scalaz

scalaz.std.MapInstances declares that any map whose value is a Semigroup is itself a Monoid . As Int is a Semigroup , the following code works:

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

However, I am surprised that the following code doesn't work:

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(_ |+| _)

Can anyone explain to me why the maps whose values are my custom Semigroup class are not considered Monoid s?

Semigroup is a type class , which means that extending it in the class representing your data isn't the intended usage.

If you're familiar with Java, think of the difference between Comparable and Comparator . If you were implementing your Num in Java and you wanted to support comparing Num values, you could either have your Num class implement Comparable[Num] , or you could provide a value of type Comparator[Num] that would describe how to compare two Num instances.

Semigroup is like Comparator , not Comparable —you don't extend it, you provide a value that describes how to append instances of your type. Note that in your version, the value parameter of the instance isn't being used in the implementation of append :

import scalaz.Semigroup

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

Instead you'd write something like this:

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

And then:

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

By putting an implicit value of type Semigroup[Num] in Num 's companion object, we're saying that that is the operation that we want to be used any time we need to add together Num instances.

Using this pattern instead of inheritance has advantages that are similar to the advantages that Comparator has over Comparable in Java: you can separate the datatype definition from the definitions of all the operations you might want to perform on that data, you can have multiple instances, etc. Scala just takes these advantages a step further by allowing you to put instances of the type class into an implicit scope so that you don't have to pass them around manually (although you still can do that if you need or want to).

If you look at other instances of semigroup, monoid or any other typeclass in scalaz you'll see that they don't use inheritance.

In fact typeclasses as a whole are a kind of alternative to inheritance, in that you can bestow behaviours on classes without having to worry about class hierarchies.

You do this by declaring an instance of some typeclass for a particular type:

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

The mechanism in scala that makes typeclasses usable is through implicit parameters.

The |+| operator implicitly asks for a Semigroup of the correct type - if it doesn't find one, it won't compile.

You're not declaring a implicit instance of Semigroup so that's why the call to |+| is not going to work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM