|+| is a semigroup, why it needs a monoid implicit resolution
Asked Answered
M

1

9

The aim of Semigroup is to make sure Associativity and closure The aim of monoid is based on Semigroup and provide additional Identity. When I use |+| semigroup appender, why I have define implicit monoid not implicit semigroup

Here is the code I am using reduceLeft which doesnt require the initial value

    val result1 = List(Staff("John", 36), Staff("Andrew", 30))
    val result2 = List(Staff("John", 40), Staff("Danny", 30))
    val result3 = List(Staff("Andrew", 30))
    val result4: List[Staff] = List()

    implicit val staffListSemigroup = new Monoid[List[Staff]] {

      override def zero: List[Staff] = Nil

      override def append(f1: List[Staff], f2: => List[Staff]): List[Staff] = {

        val mapSemigroup = f1.map(t => (t.name, t.numberOfTasks)).toMap |+| f2.map(t => (t.name, t.numberOfTasks)).toMap

        mapSemigroup.map(t => Staff(t._1, t._2)).toList

      }
    }

    val result = List(result1, result2, result3, result4).reduceLeft(_ |+| _)

    assert(result.size == 3)

if staffListSemigroup is Semigroup[List[Staff]], the compilation error is value |+| is not a member of List[SemigroupSpec.this.Staff]

Also, the definition of |+| is inside the Semigroup

final class SemigroupOps[F] private[syntax](val self: F)(implicit val F: Semigroup[F]) extends Ops[F] {
  ////
  final def |+|(other: => F): F = F.append(self, other)
  final def mappend(other: => F): F = F.append(self, other)
  final def ⊹(other: => F): F = F.append(self, other)
  ////
}

Many thanks in advance

Edit

followed by @Travis answer, I dont think it is correct. For the implicit value, the specific value will always override the generic one. Here is a code example I just wrote:

case class Foo(i : Int, s : String)
class Test[T] {
  def print = "print test"
}

implicit val test = new Test[Any]
implicit val testMoreSpecific = new Test[Foo] {
  override def print = "print Foo"
}

def doStuff[A](implicit test: Test[A]) = {
  test.print
}

doStuff[Foo] //it successfully print out print Foo
doStuff //compilation error, ambiguous implicit value found

Is that because in the Scalaz, no type specified in the method like Foo is not specified.

Milone answered 24/8, 2014 at 2:6 Comment(0)
C
8

The problem is that there's already a Semigroup for List[A] for any A. You've defined a more specific instance for List[Staff], which leads to an ambiguity, as you can see by asking for the instance:

scala> Semigroup[List[Staff]]
<console>:17: error: ambiguous implicit values:
 both method listMonoid in trait ListInstances of type [A]=> scalaz.Monoid[List[A]]
 and value staffListSemigroup of type => scalaz.Semigroup[List[Staff]]
 match expected type scalaz.Semigroup[List[Staff]]
              Semigroup[List[Staff]]
                       ^

You could jump through some hoops and try to keep the Scalaz-provided instance out of scope, but please don't!—it's potentially very confusing for other users and is a violation of some basic good principles for working with type classes. Instead you can write a wrapper for List[Staff] (a simple case class would do) and then provide an instance for that type.


For the sake of completeness, it's worth noting that the version with Monoid compiles because Monoid is more specific than Semigroup (see section 6.26.3 of the language specification for the rules that apply here, but be warned that they're kind of a mess).

Chinchin answered 24/8, 2014 at 12:39 Comment(6)
Hey Travis, I dont think it is because ambiguous, because implicit value specific type can override generic ones, I add one example to clarify that.Milone
@Cloudtech: But listMonoid doesn't provide a Monoid[List[Any]]—it provides a generic Monoid[List[A]] for whatever A.Chinchin
So does that means we should never define implicit semigroup for the List ?Milone
That depends on what you mean by "should". You can, if you keep Scalaz's listMonoid out of scope, and this may work just fine for you. In general it's a bad idea, though.Chinchin
Yes, the code looks weird to exclude listMonoid. Thanks for your answer.Milone
@Cloudtech: Just to be clear: the problem isn't only about the workaround "looking weird"—you won't necessarily always have control over what instance is in scope, and mixing and matching instances with different behavior can lead to very confusing bugs.Chinchin

© 2022 - 2024 — McMap. All rights reserved.