Skip to content

Commit b91e9bb

Browse files
coltfredLukaJCB
authored andcommitted
Add distributive typeclass and some instances (#2046)
* Add distributive typeclass and some instances * Add syntax to functor + distributive * Scalastyle * add Tuple2K instance * Move Functor requirement off the class * WIP laws cosequence and composition * Add rest of the tests * tuple2k test * Add Mima exceptions * address code review * Expand test and fix scalastyle * add more mima exceptions
1 parent 31b565b commit b91e9bb

File tree

17 files changed

+226
-7
lines changed

17 files changed

+226
-7
lines changed

build.sbt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,18 @@ def mimaSettings(moduleName: String) = Seq(
323323
exclude[ReversedMissingMethodProblem]("cats.MonadError.rethrow"),
324324
exclude[ReversedMissingMethodProblem]("cats.syntax.MonadErrorSyntax.catsSyntaxMonadErrorRethrow"),
325325
exclude[DirectMissingMethodProblem]("cats.data.CokleisliArrow.id"),
326-
exclude[IncompatibleResultTypeProblem]("cats.data.CokleisliArrow.id")
326+
exclude[IncompatibleResultTypeProblem]("cats.data.CokleisliArrow.id"),
327+
exclude[InheritedNewAbstractMethodProblem]("cats.Distributive#ToDistributiveOps.toDistributiveOps"),
328+
exclude[InheritedNewAbstractMethodProblem]("cats.syntax.DistributiveSyntax.catsSyntaxDistributiveOps"),
329+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function0Instances0.function0Distributive"),
330+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function1Instances0.functior1Distributive"),
331+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function0Instances0.function0Distributive"),
332+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function1Instances0.functior1Distributive"),
333+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function1Instances0.functior1Distributive"),
334+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function0Instances0.function0Distributive"),
335+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function1Instances0.catsStdDistributiveForFunction1"),
336+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function1Instances0.catsStdDistributiveForFunction1"),
337+
exclude[InheritedNewAbstractMethodProblem]("cats.instances.Function1Instances0.catsStdDistributiveForFunction1")
327338
)
328339
}
329340
)

core/src/main/scala/cats/Composed.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
package cats
22

33

4+
private[cats] trait ComposedDistributive[F[_], G[_]] extends Distributive[λ[α => F[G[α]]]] with ComposedFunctor[F, G] { outer =>
5+
def F: Distributive[F]
6+
def G: Distributive[G]
7+
8+
override def distribute[H[_]: Functor, A, B](ha: H[A])(f: A => F[G[B]]): F[G[H[B]]] =
9+
F.map(F.distribute(ha)(f))(G.cosequence(_))
10+
}
11+
412
private[cats] trait ComposedInvariant[F[_], G[_]] extends Invariant[λ[α => F[G[α]]]] { outer =>
513
def F: Invariant[F]
614
def G: Invariant[G]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cats
2+
import simulacrum.typeclass
3+
4+
@typeclass trait Distributive[F[_]] extends Functor[F] { self =>
5+
6+
/**
7+
* Given a function which returns a distributive `F`, apply that value across the structure G.
8+
*/
9+
def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => F[B]): F[G[B]]
10+
11+
/**
12+
* Given a Functor G which wraps some distributive F, distribute F across the G.
13+
*/
14+
def cosequence[G[_]: Functor, A](ga: G[F[A]]): F[G[A]] = distribute(ga)(identity)
15+
16+
// Distributive composes
17+
def compose[G[_]](implicit G0: Distributive[G]): Distributive[λ[α => F[G[α]]]] =
18+
new ComposedDistributive[F, G] {
19+
implicit def F = self
20+
implicit def G = G0
21+
}
22+
}

core/src/main/scala/cats/data/Kleisli.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,12 @@ private[data] sealed abstract class KleisliInstances6 extends KleisliInstances7
228228
new KleisliApply[F, A] { def F: Apply[F] = A }
229229
}
230230

231-
private[data] sealed abstract class KleisliInstances7 {
231+
private[data] sealed abstract class KleisliInstances7 extends KleisliInstances8 {
232+
implicit def catsDataDistributiveForKleisli[F[_], R](implicit F0: Distributive[F]): Distributive[Kleisli[F, R, ?]] =
233+
new KleisliDistributive[F, R] { implicit def F: Distributive[F] = F0 }
234+
}
235+
236+
private[data] sealed abstract class KleisliInstances8 {
232237
implicit def catsDataFunctorForKleisli[F[_], A](implicit F0: Functor[F]): Functor[Kleisli[F, A, ?]] =
233238
new KleisliFunctor[F, A] { def F: Functor[F] = F0 }
234239
}
@@ -379,3 +384,13 @@ private[data] trait KleisliFunctor[F[_], A] extends Functor[Kleisli[F, A, ?]] {
379384
override def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] =
380385
fa.map(f)
381386
}
387+
388+
private trait KleisliDistributive[F[_], R] extends Distributive[Kleisli[F, R, ?]] {
389+
implicit def F: Distributive[F]
390+
391+
override def distribute[G[_]: Functor, A, B](a: G[A])(f: A => Kleisli[F, R, B]): Kleisli[F, R, G[B]] =
392+
Kleisli(r => F.distribute(a)(f(_) run r))
393+
394+
395+
def map[A, B](fa: Kleisli[F, R, A])(f: A => B): Kleisli[F, R, B] = fa.map(f)
396+
}

core/src/main/scala/cats/data/Nested.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ private[data] sealed abstract class NestedInstances8 extends NestedInstances9 {
139139
new NestedApply[F, G] {
140140
val FG: Apply[λ[α => F[G[α]]]] = Apply[F].compose[G]
141141
}
142+
143+
implicit def catsDataDistributiveForNested[F[_]: Distributive, G[_]: Distributive]: Distributive[Nested[F, G, ?]] =
144+
new NestedDistributive[F, G] {
145+
val FG: Distributive[λ[α => F[G[α]]]] = Distributive[F].compose[G]
146+
}
142147
}
143148

144149
private[data] sealed abstract class NestedInstances9 extends NestedInstances10 {
@@ -245,6 +250,13 @@ private[data] trait NestedTraverse[F[_], G[_]] extends Traverse[Nested[F, G, ?]]
245250
Applicative[H].map(FG.traverse(fga.value)(f))(Nested(_))
246251
}
247252

253+
private[data] trait NestedDistributive[F[_], G[_]] extends Distributive[Nested[F, G, ?]] with NestedFunctor[F, G] {
254+
def FG: Distributive[λ[α => F[G[α]]]]
255+
256+
def distribute[H[_]: Functor, A, B](ha: H[A])(f: A => Nested[F, G, B]): Nested[F, G, H[B]] =
257+
Nested(FG.distribute(ha) { a => f(a).value })
258+
}
259+
248260
private[data] trait NestedReducible[F[_], G[_]] extends Reducible[Nested[F, G, ?]] with NestedFoldable[F, G] {
249261
def FG: Reducible[λ[α => F[G[α]]]]
250262

core/src/main/scala/cats/data/Tuple2K.scala

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,14 @@ private[data] sealed abstract class Tuple2KInstances6 extends Tuple2KInstances7
108108
}
109109
}
110110

111-
private[data] sealed abstract class Tuple2KInstances7 {
111+
private[data] sealed abstract class Tuple2KInstances7 extends Tuple2KInstances8 {
112+
implicit def catsDataDistributiveForTuple2K[F[_], G[_]](implicit FF: Distributive[F], GG: Distributive[G]): Distributive[λ[α => Tuple2K[F, G, α]]] = new Tuple2KDistributive[F, G] {
113+
def F: Distributive[F] = FF
114+
def G: Distributive[G] = GG
115+
}
116+
}
112117

118+
private[data] sealed abstract class Tuple2KInstances8 {
113119
implicit def catsDataFunctorForTuple2K[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[λ[α => Tuple2K[F, G, α]]] = new Tuple2KFunctor[F, G] {
114120
def F: Functor[F] = FF
115121
def G: Functor[G] = GG
@@ -122,6 +128,14 @@ private[data] sealed trait Tuple2KFunctor[F[_], G[_]] extends Functor[λ[α => T
122128
override def map[A, B](fa: Tuple2K[F, G, A])(f: A => B): Tuple2K[F, G, B] = Tuple2K(F.map(fa.first)(f), G.map(fa.second)(f))
123129
}
124130

131+
132+
private[data] sealed trait Tuple2KDistributive[F[_], G[_]] extends Distributive[λ[α => Tuple2K[F, G, α]]] {
133+
def F: Distributive[F]
134+
def G: Distributive[G]
135+
override def distribute[H[_]: Functor, A, B](ha: H[A])(f: A => Tuple2K[F, G, B]): Tuple2K[F, G, H[B]] = Tuple2K(F.distribute(ha){a => f(a).first}, G.distribute(ha){a => f(a).second})
136+
override def map[A, B](fa: Tuple2K[F, G, A])(f: A => B): Tuple2K[F, G, B] = Tuple2K(F.map(fa.first)(f), G.map(fa.second)(f))
137+
}
138+
125139
private[data] sealed trait Tuple2KContravariant[F[_], G[_]] extends Contravariant[λ[α => Tuple2K[F, G, α]]] {
126140
def F: Contravariant[F]
127141
def G: Contravariant[G]

core/src/main/scala/cats/instances/function.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import annotation.tailrec
1010
trait FunctionInstances extends cats.kernel.instances.FunctionInstances
1111
with Function0Instances with Function1Instances
1212

13-
private[instances] sealed trait Function0Instances {
13+
private[instances] sealed trait Function0Instances extends Function0Instances0 {
1414
implicit val catsStdBimonadForFunction0: Bimonad[Function0] =
1515
new Bimonad[Function0] {
1616
def extract[A](x: () => A): A = x()
@@ -35,6 +35,14 @@ private[instances] sealed trait Function0Instances {
3535
}
3636
}
3737

38+
private[instances] sealed trait Function0Instances0 {
39+
implicit def function0Distributive: Distributive[Function0] = new Distributive[Function0] {
40+
def distribute[F[_]: Functor, A, B](fa: F[A])(f: A => Function0[B]): Function0[F[B]] = {() => Functor[F].map(fa)(a => f(a)()) }
41+
42+
def map[A, B](fa: Function0[A])(f: A => B): Function0[B] = () => f(fa())
43+
}
44+
}
45+
3846
private[instances] sealed trait Function1Instances extends Function1Instances0 {
3947
implicit def catsStdContravariantMonoidalForFunction1[R: Monoid]: ContravariantMonoidal[? => R] =
4048
new ContravariantMonoidal[? => R] {
@@ -68,6 +76,7 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 {
6876
}
6977
}
7078

79+
7180
implicit val catsStdInstancesForFunction1: Choice[Function1] with CommutativeArrow[Function1] =
7281
new Choice[Function1] with CommutativeArrow[Function1] {
7382
def choice[A, B, C](f: A => C, g: B => C): Either[A, B] => C = {
@@ -92,11 +101,16 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 {
92101
Category[Function1].algebraK
93102
}
94103

95-
96104
private[instances] sealed trait Function1Instances0 {
97105
implicit def catsStdContravariantForFunction1[R]: Contravariant[? => R] =
98106
new Contravariant[? => R] {
99107
def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R =
100108
fa.compose(f)
101109
}
110+
111+
implicit def catsStdDistributiveForFunction1[T1]: Distributive[T1 => ?] = new Distributive[T1 => ?] {
112+
def distribute[F[_]: Functor, A, B](fa: F[A])(f: A => (T1 => B)): T1 => F[B] = {t1 => Functor[F].map(fa)(a => f(a)(t1)) }
113+
114+
def map[A, B](fa: T1 => A)(f: A => B): T1 => B = {t1 => f(fa(t1))}
115+
}
102116
}

core/src/main/scala/cats/package.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ package object cats {
3333
*/
3434
type Id[A] = A
3535
type Endo[A] = A => A
36-
implicit val catsInstancesForId: Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] =
37-
new Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] {
36+
implicit val catsInstancesForId: Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] with Distributive[Id] =
37+
new Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] with Distributive[Id] {
3838
def pure[A](a: A): A = a
3939
def extract[A](a: A): A = a
4040
def flatMap[A, B](a: A)(f: A => B): B = f(a)
@@ -43,6 +43,7 @@ package object cats {
4343
case Left(a1) => tailRecM(a1)(f)
4444
case Right(b) => b
4545
}
46+
override def distribute[F[_], A, B](fa: F[A])(f: A => B)(implicit F: Functor[F]): Id[F[B]] = F.map(fa)(f)
4647
override def map[A, B](fa: A)(f: A => B): B = f(fa)
4748
override def ap[A, B](ff: A => B)(fa: A): B = ff(fa)
4849
override def flatten[A](ffa: A): A = ffa
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package cats
2+
package syntax
3+
4+
import cats.evidence.===
5+
6+
trait DistributiveSyntax extends Distributive.ToDistributiveOps {
7+
implicit final def catsSyntaxDistributiveOps[F[_]: Functor, A](fa: F[A]): DistributiveOps[F, A] = new DistributiveOps[F, A](fa)
8+
}
9+
10+
// Add syntax to functor as part of importing distributive syntax.
11+
final class DistributiveOps[F[_], A](val fa: F[A]) extends AnyVal {
12+
def distribute[G[_], B](f: A => G[B])(implicit G: Distributive[G], F: Functor[F]): G[F[B]] = G.distribute(fa)(f)
13+
def cosequence[G[_], B](implicit G: Distributive[G], F: Functor[F], ev: A === G[B]): G[F[B]] = G.cosequence(ev.substitute(fa))
14+
}

core/src/main/scala/cats/syntax/all.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ trait AllSyntax
1515
with ComonadSyntax
1616
with ComposeSyntax
1717
with ContravariantSyntax
18+
with DistributiveSyntax
1819
with ContravariantMonoidalSyntax
1920
with ContravariantSemigroupalSyntax
2021
with EitherKSyntax

0 commit comments

Comments
 (0)