-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Parallel type class #1837
Add Parallel type class #1837
Changes from 54 commits
eaf34e6
21942c5
b37732b
664988a
e7f6f68
f37500a
447d929
4259554
6c5efc9
d7ba338
a7bea82
1556909
b3470db
b458b3d
2815a5b
7844788
378549f
1490fe4
2dc71fe
00664ae
f941a0c
1236205
17c0bc0
1502087
f1b1c1a
9252e36
e8eb35d
1a99aab
ae03b33
11caba0
1027a41
942a152
6354e08
26b7930
9e3891d
4dbc995
61b7cc7
50c5732
c661860
ccc5f45
3a300b4
ecc8e50
4882401
50c9619
db973c9
615a1a5
7395c0a
a8afdfe
7f91072
c5e3423
15d9a45
ad8a7c4
4151e7e
87f7b87
4a9f2ad
7a6cd52
cca85ea
4bbd46e
0c87a1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
package cats | ||
|
||
import cats.arrow.FunctionK | ||
|
||
/** | ||
* Some types that form a FlatMap, are also capable of forming an Apply that supports parallel composition. | ||
* The NonEmptyParallel type class allows us to represent this relationship. | ||
*/ | ||
trait NonEmptyParallel[M[_], F[_]] extends Serializable { | ||
/** | ||
* The Apply instance for F[_] | ||
*/ | ||
def apply: Apply[F] | ||
|
||
/** | ||
* The FlatMap instance for M[_] | ||
*/ | ||
def flatMap: FlatMap[M] | ||
|
||
/** | ||
* Natural Transformation from the parallel Apply F[_] to the sequential FlatMap M[_]. | ||
*/ | ||
def sequential: F ~> M | ||
|
||
/** | ||
* Natural Transformation from the sequential FlatMap M[_] to the parallel Apply F[_]. | ||
*/ | ||
def parallel: M ~> F | ||
|
||
} | ||
|
||
/** | ||
* Some types that form a Monad, are also capable of forming an Applicative that supports parallel composition. | ||
* The Parallel type class allows us to represent this relationship. | ||
*/ | ||
trait Parallel[M[_], F[_]] extends NonEmptyParallel[M, F] { | ||
/** | ||
* The applicative instance for F[_] | ||
*/ | ||
def applicative: Applicative[F] | ||
|
||
/** | ||
* The monad instance for M[_] | ||
*/ | ||
def monad: Monad[M] | ||
|
||
override def apply: Apply[F] = applicative | ||
|
||
override def flatMap: FlatMap[M] = monad | ||
|
||
/** | ||
* Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` | ||
* and a `MonadError[M, E]` instance. | ||
* I.e. if you have a type M[_], that supports parallel composition through type F[_], | ||
* then you can get `ApplicativeError[F, E]` from `MonadError[M, E]`. | ||
*/ | ||
def applicativeError[E](implicit E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { | ||
|
||
def raiseError[A](e: E): F[A] = | ||
parallel(MonadError[M, E].raiseError(e)) | ||
|
||
def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { | ||
val ma = MonadError[M, E].handleErrorWith(sequential(fa))(f andThen sequential.apply) | ||
parallel(ma) | ||
} | ||
|
||
def pure[A](x: A): F[A] = applicative.pure(x) | ||
|
||
def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = applicative.ap(ff)(fa) | ||
|
||
override def map[A, B](fa: F[A])(f: (A) => B): F[B] = applicative.map(fa)(f) | ||
|
||
override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = applicative.product(fa, fb) | ||
|
||
override def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = applicative.map2(fa, fb)(f) | ||
|
||
override def map2Eval[A, B, Z](fa: F[A], fb: Eval[F[B]])(f: (A, B) => Z): Eval[F[Z]] = | ||
applicative.map2Eval(fa, fb)(f) | ||
|
||
override def unlessA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.unlessA(cond)(f) | ||
|
||
override def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.whenA(cond)(f) | ||
} | ||
} | ||
|
||
object NonEmptyParallel { | ||
def apply[M[_], F[_]](implicit P: NonEmptyParallel[M, F]): NonEmptyParallel[M, F] = P | ||
} | ||
|
||
object Parallel extends ParallelArityFunctions { | ||
|
||
def apply[M[_], F[_]](implicit P: Parallel[M, F]): Parallel[M, F] = P | ||
|
||
/** | ||
* Like `Traverse[A].sequence`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parSequence[T[_]: Traverse, M[_], F[_], A] | ||
(tma: T[M[A]])(implicit P: Parallel[M, F]): M[T[A]] = { | ||
val fta: F[T[A]] = Traverse[T].traverse(tma)(P.parallel.apply)(P.applicative) | ||
P.sequential(fta) | ||
} | ||
|
||
/** | ||
* Like `Traverse[A].traverse`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parTraverse[T[_]: Traverse, M[_], F[_], A, B] | ||
(ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[T[B]] = { | ||
val gtb: F[T[B]] = Traverse[T].traverse(ta)(f andThen P.parallel.apply)(P.applicative) | ||
P.sequential(gtb) | ||
} | ||
|
||
/** | ||
* Like `Traverse[A].flatTraverse`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parFlatTraverse[T[_]: Traverse: FlatMap, M[_], F[_], A, B] | ||
(ta: T[A])(f: A => M[T[B]])(implicit P: Parallel[M, F]): M[T[B]] = { | ||
val gtb: F[T[B]] = Traverse[T].flatTraverse(ta)(f andThen P.parallel.apply)(P.applicative, FlatMap[T]) | ||
P.sequential(gtb) | ||
} | ||
|
||
/** | ||
* Like `Traverse[A].flatSequence`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parFlatSequence[T[_]: Traverse: FlatMap, M[_], F[_], A] | ||
(tma: T[M[T[A]]])(implicit P: Parallel[M, F]): M[T[A]] = { | ||
val fta: F[T[A]] = Traverse[T].flatTraverse(tma)(P.parallel.apply)(P.applicative, FlatMap[T]) | ||
P.sequential(fta) | ||
} | ||
|
||
/** | ||
* Like `Foldable[A].sequence_`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parSequence_[T[_]: Foldable, M[_], F[_], A] | ||
(tma: T[M[A]])(implicit P: Parallel[M, F]): M[Unit] = { | ||
val fu: F[Unit] = Foldable[T].traverse_(tma)(P.parallel.apply)(P.applicative) | ||
P.sequential(fu) | ||
} | ||
|
||
/** | ||
* Like `Foldable[A].traverse_`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parTraverse_[T[_]: Foldable, M[_], F[_], A, B] | ||
(ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[Unit] = { | ||
val gtb: F[Unit] = Foldable[T].traverse_(ta)(f andThen P.parallel.apply)(P.applicative) | ||
P.sequential(gtb) | ||
} | ||
|
||
/** | ||
* Like `NonEmptyTraverse[A].nonEmptySequence`, but uses the apply instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parNonEmptySequence[T[_]: NonEmptyTraverse, M[_], F[_], A] | ||
(tma: T[M[A]])(implicit P: NonEmptyParallel[M, F]): M[T[A]] = { | ||
val fta: F[T[A]] = NonEmptyTraverse[T].nonEmptyTraverse(tma)(P.parallel.apply)(P.apply) | ||
P.sequential(fta) | ||
} | ||
|
||
/** | ||
* Like `NonEmptyTraverse[A].nonEmptyTraverse`, but uses the apply instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parNonEmptyTraverse[T[_]: NonEmptyTraverse, M[_], F[_], A, B] | ||
(ta: T[A])(f: A => M[B])(implicit P: NonEmptyParallel[M, F]): M[T[B]] = { | ||
val gtb: F[T[B]] = NonEmptyTraverse[T].nonEmptyTraverse(ta)(f andThen P.parallel.apply)(P.apply) | ||
P.sequential(gtb) | ||
} | ||
|
||
|
||
/** | ||
* Like `NonEmptyTraverse[A].nonEmptyFlatTraverse`, but uses the apply instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parNonEmptyFlatTraverse[T[_]: NonEmptyTraverse: FlatMap, M[_], F[_], A, B] | ||
(ta: T[A])(f: A => M[T[B]])(implicit P: NonEmptyParallel[M, F]): M[T[B]] = { | ||
val gtb: F[T[B]] = NonEmptyTraverse[T].nonEmptyFlatTraverse(ta)(f andThen P.parallel.apply)(P.apply, FlatMap[T]) | ||
P.sequential(gtb) | ||
} | ||
|
||
|
||
/** | ||
* Like `NonEmptyTraverse[A].nonEmptyFlatSequence`, but uses the apply instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parNonEmptyFlatSequence[T[_]: NonEmptyTraverse: FlatMap, M[_], F[_], A] | ||
(tma: T[M[T[A]]])(implicit P: NonEmptyParallel[M, F]): M[T[A]] = { | ||
val fta: F[T[A]] = NonEmptyTraverse[T].nonEmptyFlatTraverse(tma)(P.parallel.apply)(P.apply, FlatMap[T]) | ||
P.sequential(fta) | ||
} | ||
|
||
/** | ||
* Like `Reducible[A].nonEmptySequence_`, but uses the apply instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parNonEmptySequence_[T[_]: Reducible, M[_], F[_], A] | ||
(tma: T[M[A]])(implicit P: NonEmptyParallel[M, F]): M[Unit] = { | ||
val fu: F[Unit] = Reducible[T].nonEmptyTraverse_(tma)(P.parallel.apply)(P.apply) | ||
P.sequential(fu) | ||
} | ||
|
||
/** | ||
* Like `Reducible[A].nonEmptyTraverse_`, but uses the apply instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parNonEmptyTraverse_[T[_]: Reducible, M[_], F[_], A, B] | ||
(ta: T[A])(f: A => M[B])(implicit P: NonEmptyParallel[M, F]): M[Unit] = { | ||
val gtb: F[Unit] = Reducible[T].nonEmptyTraverse_(ta)(f andThen P.parallel.apply)(P.apply) | ||
P.sequential(gtb) | ||
} | ||
|
||
/** | ||
* Like `Applicative[F].ap`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parAp[M[_], F[_], A, B](mf: M[A => B]) | ||
(ma: M[A]) | ||
(implicit P: NonEmptyParallel[M, F]): M[B] = | ||
P.sequential(P.apply.ap(P.parallel(mf))(P.parallel(ma))) | ||
|
||
/** | ||
* Like `Applicative[F].product`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parProduct[M[_], F[_], A, B](ma: M[A], mb: M[B]) | ||
(implicit P: NonEmptyParallel[M, F]): M[(A, B)] = | ||
P.sequential(P.apply.product(P.parallel(ma), P.parallel(mb))) | ||
|
||
/** | ||
* Like `Applicative[F].ap2`, but uses the applicative instance | ||
* corresponding to the Parallel instance instead. | ||
*/ | ||
def parAp2[M[_], F[_], A, B, Z](ff: M[(A, B) => Z]) | ||
(ma: M[A], mb: M[B]) | ||
(implicit P: NonEmptyParallel[M, F]): M[Z] = | ||
P.sequential( | ||
P.apply.ap2(P.parallel(ff))(P.parallel(ma), P.parallel(mb)) | ||
) | ||
|
||
/** | ||
* Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` | ||
* and a `MonadError[M, E]` instance. | ||
* I.e. if you have a type M[_], that supports parallel composition through type F[_], | ||
* then you can get `ApplicativeError[F, E]` from `MonadError[M, E]`. | ||
*/ | ||
def applicativeError[M[_], F[_], E] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when I made the suggestion, I was thinking having this method on Parallel instance. I.e.
which feels a natural addition to the existing API. Your API has benefits too. What do you think? Maybe we have both API? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think yours is definitely more useful, but I'd be okay with having both :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
(implicit P: Parallel[M, F], E: MonadError[M, E]): ApplicativeError[F, E] = P.applicativeError | ||
|
||
/** | ||
* A Parallel instance for any type `M[_]` that supports parallel composition through itself. | ||
* Can also be used for giving `Parallel` instances to types that do not support parallel composition, | ||
* but are required to have an instance of `Parallel` defined, | ||
* in which case parallel composition will actually be sequential. | ||
*/ | ||
def identity[M[_]: Monad]: Parallel[M, M] = new Parallel[M, M] { | ||
|
||
val monad: Monad[M] = implicitly[Monad[M]] | ||
|
||
val applicative: Applicative[M] = implicitly[Monad[M]] | ||
|
||
val sequential: M ~> M = FunctionK.id | ||
|
||
val parallel: M ~> M = FunctionK.id | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should override basically everything here or we could de-optimize an existing
applicative
.