I was digging through the implementation of Monoids in Scalaz. I came across the |+| operator that is supposed to come out of the box if you define the append operation on Monoid. The definition of this operator is in SemigroupSyntax. That class gets to Monoid through Semigroup.
After examining these three classes I have one major question - How exactly is the comment from SemigroupSyntax achieved /** Wraps a value `self` and provides methods related to `Semigroup` */
There is some magic with implicits, calling .this
on trait and more in the SemigroupSyntax that I honestly don't understand.
I would love if someone could take the time to enlighten me.
Thank you in advance!
EDIT:
I am keen to understand the workings of this class:
package scalaz
package syntax
/** Wraps a value `self` and provides methods related to `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)
////
}
trait ToSemigroupOps {
implicit def ToSemigroupOps[F](v: F)(implicit F0: Semigroup[F]) =
new SemigroupOps[F](v)
////
////
}
trait SemigroupSyntax[F] {
implicit def ToSemigroupOps(v: F): SemigroupOps[F] = new SemigroupOps[F](v)(SemigroupSyntax.this.F)
def F: Semigroup[F]
////
def mappend(f1: F, f2: => F)(implicit F: Semigroup[F]): F = F.append(f1, f2)
////
}
And its call site in Semigroup:
val semigroupSyntax = new scalaz.syntax.SemigroupSyntax[F] { def F = Semigroup.this }
Most disorienting thing here is that there's actually two routes to getting operations on object.
First route, the default one, is by import scalaz.syntax.semigroup._
. It adds operators for all implicitly available Semigroup
instances.
Semigroup
instance creates an implementation of SemigroupSyntax
for itself, defining its F
method in terms of this
.syntax
singleton object that extends Syntaxes
trait. It is the first part of import definition.semigroup
singleton object within Syntaxes
trait used in syntax
, which extends ToSemigroupOps
. We're importing contents of this object, containing only the implicit conversion. The purpose of conversion is to capture implicitly provided Semigroup
instance and construct a wrapper, SemigroupOps
, which contains all the operations.Second route is a shortcut by import [your_semigroup_instance].semigroupSyntax._
, a call site in Semigroup
you're listed. It adds operators to a particular type, for which Semigroup
instance is.
semigroupSyntax
is an anonymous implementation of SemigroupSyntax
trait, which F
method is defined to be a particular instance of Semigroup
.SemigroupSyntax
trait itself, like ToSemigroupOps
, offers an implicit conversion to SemigroupOps
, but instead of capturing implicitly provided instance, it uses its F
method. So we get operators on type F
, using particular Semigroup
typeclass implementation.