Difference between Semigroup and SemigroupK - scala

In cats there are 2 semigroup types classes: Semigroup and SemigroupK with the latter working on type constructors.
I fail to see the advantages of the latter over the former. If I look at the list instances they are providing Monoid (although there is a MonoidK), whereas NonEmptyList is providing a SemigroupK. Note that NonEmptyList is also providing a Semigroup via the following method:
implicit def catsDataSemigroupForNonEmptyList[A]: Semigroup[NonEmptyList[A]] =
SemigroupK[NonEmptyList].algebra[A]
Why the discrepancy?
Then it seems that most semigroup operations are only available on Semigroup and not SemigroupK (there's reduceK in Reducible but that's the only one I saw, and it delegates to reduce which works on Semigroup).
So, given a type T[_], what would you gain by having both a SemigroupK[T] and a Semigroup[T[A]] for some A?
Edit
There's now an issue to remove MonoidK and SemigroupK: https://github.com/typelevel/cats/issues/1932

One thing you can do with SemigroupK which you can't do with Semigroup is to compose instances for Nested:
implicit def catsDataSemigroupKForNested[F[_]: SemigroupK, G[_]]: SemigroupK[Nested[F, G, ?]]
If you try to write an equivalent for Semigroup, I think the closest you would get is
implicit def catsDataSemigroupForNested[F[_], G[_], A](implicit sg: Semigroup[F[G[A]]]): Semigroup[F[G[A]]] // or Semigroup[Nested[F, G, A]]
which is not very useful! From search, I can't see anything else which is implemented for SemigroupK and can't be done using Semigroup, but I might have missed something.
But the main point of SemigroupK is that once you have it, you can automatically get a Semigroup too, exactly like NonEmptyList does.

Related

Why Semigroupal for types that have Monad instances don't combine?

I am trying wrap my head around Semigroupals in Cats. Following are statements from "Scala with Cats" by Underscore.
cats.Semigroupal is a type class that allows us to combine contexts
trait Semigroupal[F[_]] {
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}
The parameters fa and fb are independent of one another: we can compute them in either order before passing them to product. This is in contrast to flatMap, which imposes a strict order on its parameters.
So basically, we should be able to combine two Either contexts as well but that doesn't seem to work:
import cats.instances.either._
type ErrorOr[A] = Either[Vector[String], A]
Semigroupal[ErrorOr].product(Left(Vector("Error 1")), Left(Vector("Error 2")))
// res3: ErrorOr[Tuple2[Nothing, Nothing]] = Left(Vector("Error 1"))
If the USP of semigroupal is to eagerly execute independent operations, both eithers must be evaluated before being passed to product and yet we can't have a combined result.
We might expect product applied to Either to accumulate errors instead of fail fast. Again, perhaps surprisingly, we find that product implements the same fail‐fast behaviour as flatMap.
Isn't it contrary to the original premise of having an alternative approach to be able to combine any contexts of same type?
To ensure consistent semantics, Cats’ Monad (which extends Semigroupal) provides a standard definition of product in terms of map and flatMap.
Why implement product in terms of map and flatMap? What semantics are being referred to here?
So why bother with Semigroupal at all? The answer is that we can create useful data types that have instances of Semigroupal (and Applicative) but not Monad. This frees us to implement product in different ways.
What does this even mean?
Unfortunately, the book doesn't covers these premises in detail! Neither can I find resources online. Could anyone please explain this? TIA.
So basically, we should be able to combine two Either contexts as well but that doesn't seem to work:
It worked, as you can see the result is a valid result, it type checks.
Semigrupal just implies that given an F[A] and a F[B] it produces an F[(A, B)] it doesn't imply that it would be able to evaluate both independently or not; it may, but it may as well not. Contrary to Monad which does imply that it needs to evaluate F[A] before because to evaluate F[B] it needs the A
Isn't it contrary to the original premise of having an alternative approach to be able to combine any contexts of same type?
Is not really a different approach since Monad[F] <: Semigroupal[F], you can always call product on any Monad. Implementing a function in terms of Semigroupal just means that it is open to more types, but it doesn't change the behavior of each type.
Why implement product in terms of map and flatMap? What semantics are being referred to here?
TL;DR; consistency:
// https://github.com/typelevel/cats/blob/54b3c2a06ff4b31f3c5f84692b1a8a3fbe5ad310/laws/src/main/scala/cats/laws/FlatMapLaws.scala#L18
def flatMapConsistentApply[A, B](fa: F[A], fab: F[A => B]): IsEq[F[B]] =
fab.ap(fa) <-> fab.flatMap(f => fa.map(f))
The above laws implies that for any F[A] and for any F[A => B] as long as there exists a Monad (actually FlatMap) for F then, fab.ap(fa) is the same as fab.flatMap(f => fa.map(f))
Now, why? Multiple reasons:
The most common one is the principle of least surprise, if I have a bunch of eithers and I pass them to a generic function, no matter if it requires Monad or Applicative I expect it to fail fast, since that is the behavior of Either.
Liskov, suppose I have two functions f and g, f expects an Applicative and g a Monad, if g calls f under the hood I would expect calling both to return the same result.
Any Monad must be an Applicative, however an accumulating Applicative version of Either requires a Semigroup for the Left, whereas, the Monad instance doesn't require that.
What does this even mean?
It means that we may define another type (for example, Validated) that would only satisfy the Applicative laws but not the Monad laws, as such it can implement an accumulating version of ap
Bonus, since having this situation of having a type that is a Monad but could implement an Applicative that doesn't require sequencing, is so common. The cats maintainers created Parallel to represent that.
So instead of converting your Eithers into Validateds to combine them using mapN you can just use parMapN directly on the Eithers.

Is there infrastructure in shapeless that takes a type constructor to the power of a Nat?

To me this appears as a very basic functionality, but I can't find it in current shapeless (2.3.3).
So I am looking for a type Induction[X,F[_],N <: Nat] with
Induction[X,F,Nat._0].Out =:= X
Induction[X,F,Nat._1].Out =:= F[X]
Induction[X,F,Nat._2].Out =:= F[F[X]]
...
Maybe it's also possible to chain a function along the type construction, e.g., to construct a Point instance?
No there isn't. As you observe this most likely needs a Point-like type class to be useful. I suggest adding something like this to Kittens which depends on both shapeless and Cats.

Is it just a coincidence that Kleisli, ReaderT, and Reader are the same in Scalaz

In Scalaz
Kleisli[F, A, B] is a wrapper for A => F[B].
ReaderT[F, A, B] -- reader monad transformer -- is just an alias of Kleisli[F, A, B].
Reader[A, B] monad is a specialization of ReaderT with identity monad Id:
type Reader[A, B] = ReaderT[Id, A, B].
Is it just a coincidence or there are some deeper reasons that Kleisli, ReaderT, and Reader are isomorphic in Scalaz ?
You can think of it as arriving at the same place by two different routes. On one side you start with the reader monad, which is simply a kind of wrapper for functions. Then you realize that you want to integrate this reader functionality into a larger monad with other "effects", so you create a ReaderT monad transformer. At that point it makes sense to implement your original Reader[E, ?] as ReaderT[Id, E, ?].
From the other side, you want a type to represent Kleisli arrows (i.e. functions with a monadic return type). It turns out that this is the same thing as ReaderT, so you just make that an alias.
There's nothing terribly mysterious about the "it turns out" part. It's a little like if you started out with an Addable type class for number-like things, then decide to make it more generic, and eventually end up with a type class that just provides an associative "addition-like" operation. You've reinvented Semigroup! You may still want to keep the Addable name around, though, for historical or pedagogical reasons, or just for convenience.
That's all that's happening with Reader and ReaderT—you don't need these aliases, but they can be convenient, and may help improve the clarity of your code.

Define scalaz monad instance for a shapeless hlist

I've tried to define a Monad (scalaz) for shapeless HList through point and bind implementation. The first problem is that HList trait is not a type constructor, but that can be solved with type lambdas, point is simple, but i couldn't find right implementation for bind, i guess i need some function of type Poly1 with some Aux/Mapper tricks, but that side of shapeless is still dark to me. HList has all functions to be a Monad, like simple List, so is it possible to implement one from Scalaz?
A monoid is a set with some operations that obey particular laws. What elements are you considering as possible HListM[A]? If you declare HListM[A] = HList, i.e. any HList, then you'll quickly find that you can't map with f: A => B, except by treating all maps as identity and you've reinvented the rather uninteresting monad Id (with a few extra but inert inhabitants).
We could make a monad with the type HListM[A] = A :: ... :: A :: HNil (though even actually expressing that type in Scala is a challenge - you'd need an auxiliary trait trait CopiesOf[N <: Nat, A] {type Out <: HList}, implicits to provide instances of this, and then an existential to actually write it (CopiesOf[N, A]#Out forSome {type N <: Nat})). Writing monad operations for this is possible, though you'd need to require shapeless auxiliary classes like Prepend at the point of operation, since there's no real way to express a "forall" type in Scala - you can declare instances of your type for _0 and Succ[N], but there's no way to prove to the compiler that there is an instance for any N <: Nat, you just have to require implicit ones whenever you need to use them.
But after a lot of work you'd end up with something isomorphic to List[A]; why not just use List[A] for that case?

Variance of Ordered, PartiallyOrdered

According to the documentation, PartiallyOrdered[A] is covariant in A, while Ordered[A] is invariant (but used to be covariant) in A.
Why was Ordered[A] ever covariant in A? Isn't this an obvious violation of the substitution principle?
Why can't Ordered[A] be contravariant in A? This would allow an Ordered[Traversible[Char]] to be typed as an Ordered[StringBuilder], for example. I don't see how this could be problematic.
I'm having trouble understanding the signature of tryCompareTo in PartiallyOrdered. It looks like the argument can be an instance of any supertype of A. Couldn't you pass in any object by calling tryCompareTo[Any](anything)? If so, how is the method signature any better than tryCompareTo(that: Any)?
Logically, ordered sets are a subclass of partially ordered sets, but the Scala classes don't seem to reflect this relationship. Is this because Ordered[A] cannot be covariant in A as PartiallyOrdered[A] can?
Looking at the previous version (2.8.1) , I didn't find anything relevant about Ordered that was covariant ?
EDIT : Looked further and find some explanation in the documentation for Ordered to not be covariant anymore.
For the case of tryCompare in PartiallyOrdered, the signature set more restriction on passed parameter :
def tryCompareTo [B >: A] (that: B)(implicit arg0: (B) ⇒ PartiallyOrdered[B]): Option[Int]
Here the implicit arg0 implies that to be seen as a PartiallyOrdered[B] instance and thus that it has access to all method defined in this trait.