Add distributive typeclass and some instances - #2046
Conversation
|
Hey Colt, for the syntax you can do something like this: trait DistributiveSyntax extends Distributive.ToDistributiveOps {
implicit final def catsSyntaxDistributiveOps[F[_]: Functor, A](fa: F[A]): DistributiveOps[F,
A] =
new DistributiveOps[F, A](fa)
}
final class DistributiveOps[F[_]: Functor, A](val fa: F[A]) extends AnyVal {
def distribute[G[_]: Distributive, B](f: A => G[B]): G[F[B]] = /* ... */
} |
|
I think we can also do instances for Tuple2K :) |
| package cats | ||
|
|
||
|
|
||
| private [cats] trait ComposedDistributive[F[_], G[_]] extends Distributive[λ[α => F[G[α]]]] with ComposedFunctor[F, G] { outer => |
There was a problem hiding this comment.
Scalastyle doesn't like the space before [cats] here.
| } | ||
|
|
||
| // Add syntax to functor as part of importing distributive syntax. | ||
| final class DistributiveOps[F[_]: Functor, A](val fa: F[A]) { |
There was a problem hiding this comment.
No, because the class has the Functor constraint.
There was a problem hiding this comment.
Ah yes, you're right! We should move the Functor constraint to the method instead
| import cats.evidence.=== | ||
|
|
||
| trait DistributiveSyntax extends Distributive.ToDistributiveOps { | ||
| implicit final def catsSyntaxDistributiveOps[F[_]: Functor, A](fa: F[A]): DistributiveOps[F, A] = new DistributiveOps[F, A](fa) |
There was a problem hiding this comment.
Maybe we should add another class for cosequence, we wouldn't need cats.evidence then:
final class CosequenceOps[F[_]: Functor, G[_]: Distributive, A](val fga: F[G[A]]) extends AnyVal {
def cosequence: G[F[A]] = G.cosequence(fga)
}There was a problem hiding this comment.
Hmmm, not sure I see a reason why that's better? The evidence constraint is on just the one method and it saves the extra class for syntax. Is there a reason it should be preferred?
There was a problem hiding this comment.
Maybe you're right, disregard me! :D
Codecov Report
@@ Coverage Diff @@
## master #2046 +/- ##
==========================================
+ Coverage 94.63% 94.65% +0.02%
==========================================
Files 318 322 +4
Lines 5391 5449 +58
Branches 209 215 +6
==========================================
+ Hits 5102 5158 +56
- Misses 289 291 +2
Continue to review full report at Codecov.
|
|
Laws and tests are all that's needed. Sorry I haven't been able to hammer those out it's been a busy week. I'll try and get the laws written tomorrow. |
|
@LukaJCB I think everything should be in there, now. Let me know if you have problems with anything. |
|
Hey Colt, looks really great, thanks again! I think it's ready to merge, but you'll need to add some MiMa Exceptions :) |
| } | ||
|
|
||
| private[data] sealed abstract class KleisliInstances8 { | ||
| implicit def kleisliDistributive[F[_], R](implicit F0: Distributive[F]): Distributive[Kleisli[F, R, ?]] = |
There was a problem hiding this comment.
The Cats convention is that instances that are more specific should have higher priority. So we should probably swap the Functor and Distributive instance here.
Also, according to naming convention of implicits, it should be catsDataDistributiveForKleisli
|
|
||
| private[data] sealed abstract class Tuple2KInstances8 { | ||
|
|
||
| implicit def catsDataDistributiveForTuple2K[F[_], G[_]](implicit FF: Distributive[F], GG: Distributive[G]): Distributive[λ[α => Tuple2K[F, G, α]]] = new Tuple2KDistributive[F, G] { |
There was a problem hiding this comment.
Same as in Kleisli, need to swap the two instances.
| val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeContravariant[G] | ||
| } | ||
|
|
||
| implicit def catsDataDistributiveForNested[F[_]: Distributive, G[_]: Distributive]: Distributive[Nested[F, G, ?]] = |
There was a problem hiding this comment.
It needs to go above the Functor instance
| fa.compose(f) | ||
| } | ||
|
|
||
| implicit def functior1Distributive[T1]: Distributive[T1 => ?] = new Distributive[T1 => ?] { |
There was a problem hiding this comment.
functior1Distributive -> catsStdDistributiveForFunction1
|
|
||
| { | ||
| checkAll("Kleisli[Function0, Int, ?]", DistributiveTests[Kleisli[Function0, Int, ?]].distributive[Int, Int, Int, Option, Id]) | ||
| checkAll("Distributive[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Distributive[Kleisli[Function0, Int, ?]])) |
There was a problem hiding this comment.
Nit: It should be Distributive[Kleisli[Function0, Int, ?]]
|
|
||
| { | ||
| //Distributive composition | ||
| checkAll("Nested[Function0, Function0, ?]", DistributiveTests[Nested[Function0, Function0, ?]].distributive[Int, Int, Int, Option, Function0]) |
There was a problem hiding this comment.
I wonder if we should test composition of two different data types like the Serializable test below, i.e. Nested[Function1[Int, ?], Function0, ?]
There was a problem hiding this comment.
I ran into an error about Eq instances because distributive in the Tests needs to be able to compare. I gave up and thought this was a reasonable compromise.
There was a problem hiding this comment.
Ah, that's due to the Eq instance for Function1 isn't provided by default
You can do the following
import cats.laws.discipline.eq._
//Distributive composition
checkAll("Nested[Function1[Int, ?], Function0, ?]", DistributiveTests[Nested[Function1[Int, ?], Function0, ?]].distributive[Int, Int, Int, Option, Function0])
That passes.
|
scalastyle
|
Things still left:
I started on it this weekend and wanted to push it up to start getting feedback as well as help on the syntax. Distributive is weird in that it's adding syntax to the
Functor, not to the type that has theDistributiveinstance.We want to add
distribute[G[_]: Distributive, A, B](f: A => G[B]): G[F[B]]to the functorF. This means that we should be able to turnList[Int => Int]intoInt => List[Int], but I don't know how to add the syntax to the Functor that's passed to it.