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 availableSemigroup
instances.Semigroup
instance creates an implementation ofSemigroupSyntax
for itself, defining itsF
method in terms ofthis
.syntax
singleton object that extendsSyntaxes
trait. It is the first part of import definition.semigroup
singleton object withinSyntaxes
trait used insyntax
, which extendsToSemigroupOps
. We're importing contents of this object, containing only the implicit conversion. The purpose of conversion is to capture implicitly providedSemigroup
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 inSemigroup
you're listed. It adds operators to a particular type, for whichSemigroup
instance is.semigroupSyntax
is an anonymous implementation ofSemigroupSyntax
trait, whichF
method is defined to be a particular instance ofSemigroup
.SemigroupSyntax
trait itself, likeToSemigroupOps
, offers an implicit conversion toSemigroupOps
, but instead of capturing implicitly provided instance, it uses itsF
method. So we get operators on typeF
, using particularSemigroup
typeclass implementation.