Skip to content
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

Optimize foldMap implementations with combineAll #2024

Merged
merged 1 commit into from
Nov 10, 2017

Conversation

carymrobbins
Copy link
Contributor

No description provided.

@codecov-io
Copy link

codecov-io commented Nov 10, 2017

Codecov Report

Merging #2024 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2024      +/-   ##
==========================================
+ Coverage   95.08%   95.09%   +<.01%     
==========================================
  Files         301      301              
  Lines        4946     4953       +7     
  Branches      125      117       -8     
==========================================
+ Hits         4703     4710       +7     
  Misses        243      243
Impacted Files Coverage Δ
...eycats-core/src/main/scala/alleycats/std/set.scala 100% <ø> (ø) ⬆️
...s-core/src/main/scala/alleycats/std/iterable.scala 100% <ø> (ø) ⬆️
core/src/main/scala/cats/instances/list.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/data/NonEmptyList.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/data/NonEmptyVector.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/instances/queue.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/instances/stream.scala 96.49% <100%> (+0.06%) ⬆️
core/src/main/scala/cats/instances/sortedSet.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/instances/vector.scala 71.42% <100%> (+0.69%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update aeff8a6...eb15caa. Read the comment docs.

Copy link
Contributor

@johnynek johnynek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! One question about the Set foldMap which I think should also use .iterator.

@@ -68,6 +68,9 @@ object SetInstances {
def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Foldable.iterateRight(fa, lb)(f)

override def foldMap[A, B](fa: Set[A])(f: A => B)(implicit B: Monoid[B]): B =
B.combineAll(fa.map(f))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fa.iterator.map(f) will be faster (not allocate a set) also maybe more correct. fa.map(f) if f = { _ => 1 } will create a set of 1 thing. If you do foldMap what do we mean? I actually don't know if this should return the count of items (which I think it should, which means we should do fa.iterator.map), or if it should return 0 if empty and 1 otherwise.

I guess since this is lawless especially we need a comment as to the behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, I originally avoided using .iterator to maintain the same semantics, but I forgot to confirm that was the case.

@ alleycats.std.SetInstances.setTraverse.foldMap(Set(1,2,3))(_ => "a")
res13: String = "aaa"

Seems not using .iterator actually would be a breaking change, so I'll switch to use it. Nice catch!

@kailuowang
Copy link
Contributor

kailuowang commented Nov 10, 2017

Do you guys mind to provide some enlightenment here? I must've missed something, I have trouble understanding how this optimize over the default implementation.
combineAll(as) is doing as.foldLeft(empty)(combine), replace as with as.iterator.map(f), then you get as.iterator.map(f).foldLeft(B.empty)(B.combine),
Default implementation of as.foldMap is doing
as.foldLeft(fa, B.empty)((b, a) => B.combine(b, f(a)))
Why doing map first then foldLeft with combine is better than foldLeft with combineing with f(a) ongoing?

@tpolecat
Copy link
Member

combineAll is optimized for some types, which fixes some N^2 cases (like String via StringBuilder or always concatenating lists right to left).

@kailuowang
Copy link
Contributor

thanks @tpolecat .

@johnynek
Copy link
Contributor

👍

@johnynek johnynek merged commit 31874ce into typelevel:master Nov 10, 2017
@carymrobbins carymrobbins deleted the efficient-foldMap branch November 10, 2017 22:32
@kailuowang kailuowang added this to the 1.0.0 milestone Nov 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants