-
-
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
Conversation
Oh wait. I've just realized that it is not going to work with non-empty containers like Not sure what would be the best way to improve it. One way could be to move The latter seems to be more "radical", but it would allow other cool things like describing an additional variant of The former seems to be less invasive, but I am a bit concerned about these additional So wdyt? UPD: decided to introduce |
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 really like this pr!
I agree the order of prependK should be changed.
Also, can we update all the Alternative instances? LazyList, Stream, ZipStream, ZipLazyList, Composed, Kleisli, ArraySeq, Queue, Chain. I think that's about it.
499739b
to
546f1e3
Compare
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.
Looks great!
@johnynek sorry, it is not complete yet. I'm still working on the changes you requested. |
@johnynek turns out that |
@johnynek regarding final def prepend[A2 >: A](a: A2): Chain[A2] = Chain.concat(one(a), this)
final def append[A2 >: A](a: A2): Chain[A2] = Chain.concat(this, one(a)) I.e. they do exactly the same job as Moreover, due to the nature of final def prepend[A2 >: A](a: A2): Chain[A2] =
this match {
case Chain.Wrap(seq) => Chain.Wrap(a +: seq)
case _ => Chain.concat(one(a), this)
} but I am not sure if it is really better than just to do a I'd suppose it may require additional investigations and benchmarking and is beyond the scope of this PR. |
Yeah good point about Chain. Definitely doing the wrap thing would be bad because Seq has no guarantee about either being fast. Although you could imagine pattern matching on List or Vector in there and then using optimized prepend or append respectively. But that could be costly to put that test in there. Maybe worth a benchmark. |
3c3fd1c
to
24dfea0
Compare
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.
one comment, take or leave.
Thanks for doing this!
Just an update: although the initial goal seems achieved in general from my prospective, now I'm working on addressing my aforementioned concern regarding non-empty containers. It is going to be a new type class, namely |
@johnynek regarding Unfortunately, seems there're neither laws nor docs for |
24dfea0
to
6362f07
Compare
6362f07
to
98a70eb
Compare
3f7c9bf
to
9e6ac0d
Compare
Turned out it was not an easy lift and required a way more changes that I initially anticipated. But seems the PR is competed now. Finally) I've also updated the description of the PR. There were several bin-compat issues which I had to tackle. The solutions don't look esthetically great but they're the best I managed to come up with. And seems MiMa is happy now. But if someone has got an idea on how to make it better, I'd really appreciate any help/hint on that. |
9e6ac0d
to
bd6c948
Compare
bd6c948
to
6a8f580
Compare
|
||
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] | ||
} |
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.
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 Alternative
and NonEmptyAlternative
instances (see below) are supported to keep the PR scope narrow.
This is really good work. It did get very large. I'm now wondering if we can break it into two:
It is much harder to review larger PRs and if we have to revert, we revert more code at once. I would be on the lookout for ways we can decompose into separately mergeable parts. What do you think? |
Agree, that makes sense to me too. I'll take on this. "introduce NonEmptyAlternative and the laws" – do you mean to add |
yeah, I was thinking add the default implementation of Just as one example way to split the PR. |
6a8f580
to
de59fd6
Compare
@@ -14,6 +14,7 @@ abstract class AllSyntaxBinCompat | |||
|
|||
trait AllSyntax | |||
extends AlternativeSyntax | |||
with NonEmptyAlternativeSyntax |
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.
@@ -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 comment
The 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 syntax
just in alphabetic order by their names. But later the order was broken (perhaps due to an oversight). So I'm putting the new object to the end of all existing objects for now.
de59fd6
to
6a30402
Compare
@johnynek FYI: I truncated this PR to make it containing basic |
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.
Thank you so much!
Rationale
There's a pretty common pattern can be found for
Alternative
even in the Cats sources:F.combineK(F.pure(a), fa)
andF.combineK(fa, F.pure(a))
which basically means prepending or appending an element
a
lifted intoF[_]
.But when it is used for some standard collections like
List
orVector
it basically results in creating an interim singleton collection on eachF.pure
which gets discarded right afterF.combineK
is completed.The proposed PR allows to avoid creating the interim singleton collections by various
Alternative
implementations in many cases.Description
This PR does the following:
NonEmptyAlternative
as a combination ofApplicative
andSemigroupK
.prependK
andappendK
methods toNonEmptyAlternative
. These methods have default implementations as shown above. But they can be overridden for a particularAlternative
implementation.Alternative
inheritingNonEmptyAlternative
instead of justApplicative
. TheMonoidK
mix-in type class remains in place.F.pure + F.combineK
patterns found in core Cats (apart fromcats.data
) toF.prependK
orF.appendK
appropriately where it is safe and reasonable.NonEmptyAlternativeLaws
which becomes a base forAlternativeLaws
.compose
) type-classes (seecomposed
constructor in the corresponding companion objects).Things to be done in a subsequent PR (see the discussion below):
NonEmptyAlternative
instance to non-empty containers incats.data
.F.prependK
andF.appendK
in theAlternative
andNonEmptyAlternative
implementations where it reasonable.Therefore this PR along with the next one will allow to utilize more efficient methods in some collections (e.g.
::
inList
) while preserving binary and source compatibility with existingAlternative
implementations. Moreover they should enable further optimizations for non-empty data types too.Caveats
prependK
method because it is defined in this way:Alternative
norNonEmptyAlternative
make use of extension methods generated by Simulacrum actually.