[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 |
johnynek
left a comment
There was a problem hiding this comment.
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
|
@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
johnynek
left a comment
There was a problem hiding this comment.
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.
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
|
|
||
| trait AllSyntax | ||
| extends AlternativeSyntax | ||
| with NonEmptyAlternativeSyntax |
There was a problem hiding this comment.
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.
| object vector extends VectorSyntax | ||
| object writer extends WriterSyntax | ||
| object set extends SetSyntax | ||
| object nonEmptyAlternative extends NonEmptyAlternativeSyntax |
There was a problem hiding this comment.
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 |
Rationale
There's a pretty common pattern can be found for
Alternativeeven in the Cats sources:F.combineK(F.pure(a), fa)andF.combineK(fa, F.pure(a))which basically means prepending or appending an element
alifted intoF[_].But when it is used for some standard collections like
ListorVectorit basically results in creating an interim singleton collection on eachF.purewhich gets discarded right afterF.combineKis completed.The proposed PR allows to avoid creating the interim singleton collections by various
Alternativeimplementations in many cases.Description
This PR does the following:
NonEmptyAlternativeas a combination ofApplicativeandSemigroupK.prependKandappendKmethods toNonEmptyAlternative. These methods have default implementations as shown above. But they can be overridden for a particularAlternativeimplementation.AlternativeinheritingNonEmptyAlternativeinstead of justApplicative. TheMonoidKmix-in type class remains in place.F.pure + F.combineKpatterns found in core Cats (apart fromcats.data) toF.prependKorF.appendKappropriately where it is safe and reasonable.NonEmptyAlternativeLawswhich becomes a base forAlternativeLaws.compose) type-classes (seecomposedconstructor in the corresponding companion objects).Things to be done in a subsequent PR (see the discussion below):
NonEmptyAlternativeinstance to non-empty containers incats.data.F.prependKandF.appendKin theAlternativeandNonEmptyAlternativeimplementations 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 existingAlternativeimplementations. Moreover they should enable further optimizations for non-empty data types too.Caveats
prependKmethod because it is defined in this way:AlternativenorNonEmptyAlternativemake use of extension methods generated by Simulacrum actually.