-
-
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
[proposal] optimize Alternative (part 1): introduce NonEmptyAlternative with prependK and appendK methods #4014
Changes from all commits
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,84 @@ | ||
package cats | ||
|
||
import simulacrum.typeclass | ||
import scala.annotation.implicitNotFound | ||
|
||
@implicitNotFound("Could not find an instance of NonEmptyAlternative for ${F}") | ||
@typeclass trait NonEmptyAlternative[F[_]] extends Applicative[F] with SemigroupK[F] { self => | ||
|
||
/** | ||
* Lift `a` into `F[_]` and prepend it to `fa`. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> NonEmptyAlternative[List].prependK(1, List(2, 3, 4)) | ||
* res0: List[Int] = List(1, 2, 3, 4) | ||
* }}} | ||
*/ | ||
def prependK[A](a: A, fa: F[A]): F[A] = combineK(pure(a), fa) | ||
|
||
/** | ||
* Lift `a` into `F[_]` and append it to `fa`. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> NonEmptyAlternative[List].appendK(List(1, 2, 3), 4) | ||
* res0: List[Int] = List(1, 2, 3, 4) | ||
* }}} | ||
*/ | ||
def appendK[A](fa: F[A], a: A): F[A] = combineK(fa, pure(a)) | ||
|
||
override def compose[G[_]: Applicative]: NonEmptyAlternative[λ[α => F[G[α]]]] = | ||
new ComposedNonEmptyAlternative[F, G] { | ||
val F = self | ||
val G = Applicative[G] | ||
} | ||
} | ||
|
||
object NonEmptyAlternative { | ||
/* ======================================================================== */ | ||
/* THE FOLLOWING CODE IS MANAGED BY SIMULACRUM; PLEASE DO NOT EDIT!!!! */ | ||
/* ======================================================================== */ | ||
|
||
/** | ||
* Summon an instance of [[NonEmptyAlternative]] for `F`. | ||
*/ | ||
@inline def apply[F[_]](implicit instance: NonEmptyAlternative[F]): NonEmptyAlternative[F] = instance | ||
|
||
@deprecated("Use cats.syntax object imports", "2.2.0") | ||
object ops { | ||
implicit def toAllNonEmptyAlternativeOps[F[_], A](target: F[A])(implicit tc: NonEmptyAlternative[F]): AllOps[F, A] { | ||
type TypeClassType = NonEmptyAlternative[F] | ||
} = new AllOps[F, A] { | ||
type TypeClassType = NonEmptyAlternative[F] | ||
val self: F[A] = target | ||
val typeClassInstance: TypeClassType = tc | ||
} | ||
} | ||
trait Ops[F[_], A] extends Serializable { | ||
type TypeClassType <: NonEmptyAlternative[F] | ||
def self: F[A] | ||
val typeClassInstance: TypeClassType | ||
// Note: `prependK` has to be added manually since simulacrum is not able to handle `self` as a second parameter. | ||
// def prependK(a: A): F[A] = typeClassInstance.prependK[A](a, self) | ||
def appendK(a: A): F[A] = typeClassInstance.appendK[A](self, a) | ||
} | ||
trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] with SemigroupK.AllOps[F, A] { | ||
type TypeClassType <: NonEmptyAlternative[F] | ||
} | ||
trait ToNonEmptyAlternativeOps extends Serializable { | ||
implicit def toNonEmptyAlternativeOps[F[_], A](target: F[A])(implicit tc: NonEmptyAlternative[F]): Ops[F, A] { | ||
type TypeClassType = NonEmptyAlternative[F] | ||
} = new Ops[F, A] { | ||
type TypeClassType = NonEmptyAlternative[F] | ||
val self: F[A] = target | ||
val typeClassInstance: TypeClassType = tc | ||
} | ||
} | ||
@deprecated("Use cats.syntax object imports", "2.2.0") | ||
object nonInheritedOps extends ToNonEmptyAlternativeOps | ||
|
||
/* ======================================================================== */ | ||
/* END OF SIMULACRUM-MANAGED CODE */ | ||
/* ======================================================================== */ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package cats | ||
package syntax | ||
|
||
trait NonEmptyAlternativeSyntax { | ||
implicit final def catsSyntaxNonEmptyAlternative[F[_], A](fa: F[A]): NonEmptyAlternativeOps[F, A] = | ||
new NonEmptyAlternativeOps(fa) | ||
} | ||
|
||
final class NonEmptyAlternativeOps[F[_], A] private[syntax] (private val fa: F[A]) extends AnyVal { | ||
|
||
/** | ||
* @see [[NonEmptyAlternative.prependK]] | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.syntax.all._ | ||
* | ||
* scala> List(2, 3, 4).prependK(1) | ||
* res0: List[Int] = List(1, 2, 3, 4) | ||
* }}} | ||
*/ | ||
def prependK(a: A)(implicit F: NonEmptyAlternative[F]): F[A] = F.prependK(a, fa) | ||
|
||
/** | ||
* @see [[NonEmptyAlternative.appendK]] | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.syntax.all._ | ||
* | ||
* scala> List(1, 2, 3).appendK(4) | ||
* res0: List[Int] = List(1, 2, 3, 4) | ||
* }}} | ||
*/ | ||
def appendK(a: A)(implicit F: NonEmptyAlternative[F]): F[A] = F.appendK(fa, a) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,4 +71,5 @@ package object syntax { | |
object vector extends VectorSyntax | ||
object writer extends WriterSyntax | ||
object set extends SetSyntax | ||
object nonEmptyAlternative extends NonEmptyAlternativeSyntax | ||
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. Seems that the initial intent was to keep all inner objects of object |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,22 +3,28 @@ package laws | |
|
||
import cats.syntax.all._ | ||
|
||
trait AlternativeLaws[F[_]] extends ApplicativeLaws[F] with MonoidKLaws[F] { | ||
trait AlternativeLaws[F[_]] extends NonEmptyAlternativeLaws[F] with MonoidKLaws[F] { | ||
implicit override def F: Alternative[F] | ||
implicit def algebra[A]: Monoid[F[A]] = F.algebra[A] | ||
implicit override def algebra[A]: Monoid[F[A]] = F.algebra[A] | ||
|
||
def alternativeRightAbsorption[A, B](ff: F[A => B]): IsEq[F[B]] = | ||
(ff.ap(F.empty[A])) <-> F.empty[B] | ||
|
||
// Perhaps should be deprecated in favor of nonEmptyAlternativeLeftDistributivity | ||
def alternativeLeftDistributivity[A, B](fa: F[A], fa2: F[A], f: A => B): IsEq[F[B]] = | ||
((fa |+| fa2).map(f)) <-> ((fa.map(f)) |+| (fa2.map(f))) | ||
nonEmptyAlternativeLeftDistributivity[A, B](fa, fa2, f) | ||
|
||
// Perhaps should be deprecated in favor of nonEmptyAlternativeRightDistributivity | ||
def alternativeRightDistributivity[A, B](fa: F[A], ff: F[A => B], fg: F[A => B]): IsEq[F[B]] = | ||
((ff |+| fg).ap(fa)) <-> ((ff.ap(fa)) |+| (fg.ap(fa))) | ||
|
||
nonEmptyAlternativeRightDistributivity(fa, ff, fg) | ||
} | ||
|
||
object AlternativeLaws { | ||
def apply[F[_]](implicit ev: Alternative[F]): AlternativeLaws[F] = | ||
new AlternativeLaws[F] { def F: Alternative[F] = ev } | ||
|
||
def composed[M[_], N[_]](implicit M: Alternative[M], N: Applicative[N]): AlternativeLaws[λ[α => M[N[α]]]] = | ||
new AlternativeLaws[λ[α => M[N[α]]]] { | ||
def F: Alternative[λ[α => M[N[α]]]] = M.compose[N] | ||
} | ||
Comment on lines
+25
to
+29
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. Just a call-out: this introduces a new ability to validate laws for composed instances. This feature is totally new, it didn't exist before. I decided to add it because composed instances were not covered with any tests at all. But currently only composed |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package cats | ||
package laws | ||
|
||
import cats.syntax.all._ | ||
|
||
trait NonEmptyAlternativeLaws[F[_]] extends ApplicativeLaws[F] with SemigroupKLaws[F] { | ||
implicit override def F: NonEmptyAlternative[F] | ||
implicit def algebra[A]: Semigroup[F[A]] = F.algebra[A] | ||
|
||
def nonEmptyAlternativeLeftDistributivity[A, B](fa: F[A], fa2: F[A], f: A => B): IsEq[F[B]] = | ||
((fa |+| fa2).map(f)) <-> ((fa.map(f)) |+| (fa2.map(f))) | ||
|
||
def nonEmptyAlternativeRightDistributivity[A, B](fa: F[A], ff: F[A => B], fg: F[A => B]): IsEq[F[B]] = | ||
((ff |+| fg).ap(fa)) <-> ((ff.ap(fa)) |+| (fg.ap(fa))) | ||
|
||
def nonEmptyAlternativePrependKConsitentWithPureAndCombineK[A](fa: F[A], a: A): IsEq[F[A]] = | ||
fa.prependK(a) <-> (a.pure[F] <+> fa) | ||
|
||
def nonEmptyAlternativeAppendKConsitentWithPureAndCombineK[A](fa: F[A], a: A): IsEq[F[A]] = | ||
fa.appendK(a) <-> (fa <+> a.pure[F]) | ||
} | ||
|
||
object NonEmptyAlternativeLaws { | ||
def apply[F[_]](implicit ev: NonEmptyAlternative[F]): NonEmptyAlternativeLaws[F] = | ||
new NonEmptyAlternativeLaws[F] { def F: NonEmptyAlternative[F] = ev } | ||
|
||
def composed[M[_], N[_]](implicit | ||
M: NonEmptyAlternative[M], | ||
N: Applicative[N] | ||
): NonEmptyAlternativeLaws[λ[α => M[N[α]]]] = | ||
new NonEmptyAlternativeLaws[λ[α => M[N[α]]]] { | ||
def F: NonEmptyAlternative[λ[α => M[N[α]]]] = M.compose[N] | ||
} | ||
} |
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.
Not sure, where to insert this new trait. I'd prefer to maintain all mix-in traits in some particular order, but I cannot figure out any specific order here.