Skip to content

add foldMapA as an alternative to foldMapM that only requires an Applicative rather than a monad - #3130

Merged
LukaJCB merged 1 commit into
typelevel:masterfrom
mberndt123:mb/foldmapm
Nov 9, 2019
Merged

add foldMapA as an alternative to foldMapM that only requires an Applicative rather than a monad#3130
LukaJCB merged 1 commit into
typelevel:masterfrom
mberndt123:mb/foldmapm

Conversation

@mberndt123

Copy link
Copy Markdown
Contributor

Applicative is enough for this method, so it shouldn't require Monad.

@travisbrown

Copy link
Copy Markdown
Contributor

This could probably be done in a binary-compatible way by making the current implementation package private and adding the new one as an overload. I agree with @LukaJCB that it probably makes more sense to add a new foldMapA with the appropriate constraint instead.

It seems like the bigger issue with this implementation is short circuiting. For example, this will hang forever with the foldMap implementation:

import cats.implicits._

Stream.iterate(0)(_ + 1).foldMapM(b => if (b > 10) Left(b) else Right(b))

I think an implementation like this should be fine both in terms of stack safety and short-circuiting:

def foldMapA[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Applicative[G], B: Monoid[B]): G[B] =
  foldRight(fa, Eval.now(G.pure(B.empty)))((a, egb) =>
    G.map2Eval(f(a), egb)(B.combine)
  ).value

For example:

scala> import cats.implicits._
import cats.implicits._

scala> Stream.iterate(0)(_ + 1).foldMapA(b => if (b > 10) Left(b) else Right(b))
res0: scala.util.Either[Int,Int] = Left(11)

scala> Stream.iterate(0)(_ + 1).foldMapA(b => if (b > 1000000) Left(b) else Right(b))
res1: scala.util.Either[Int,Int] = Left(1000001)

@mberndt123

Copy link
Copy Markdown
Contributor Author

Hey there,

Thanks for the review! I didn't know about map2Eval, that's very interesting.

I've now added foldMapA as a new method and also a test to make sure the short-circuiting behaviour is preserved.

@codecov-io

codecov-io commented Nov 7, 2019

Copy link
Copy Markdown

Codecov Report

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

Impacted file tree graph

@@            Coverage Diff            @@
##           master   #3130      +/-   ##
=========================================
+ Coverage   93.19%   93.2%   +<.01%     
=========================================
  Files         376     376              
  Lines        7323    7324       +1     
  Branches      190     186       -4     
=========================================
+ Hits         6825    6826       +1     
  Misses        498     498
Flag Coverage Δ
#scala_version_212 93.53% <100%> (ø) ⬆️
#scala_version_213 90.85% <100%> (-0.02%) ⬇️
Impacted Files Coverage Δ
core/src/main/scala/cats/Foldable.scala 100% <100%> (ø) ⬆️

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 7fb1327...6f9b8cc. Read the comment docs.

@travisbrown travisbrown left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should merge this, although I don't completely like the idea of having two methods with identical semantics but different constraints and (probably) different performance expectations.

/**
* Equivalent to foldMapM.
* The difference is that foldMapA only requires G to be an Applicative
* rather than a Monad. It is also slower due to use of Eval.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we actually know that this is true? My intuition is that it generally will be, but there's also some Either overhead involved in the tailRecM implementation.

@travisbrown

Copy link
Copy Markdown
Contributor

I put together a little benchmark with a vector with 10k elements with examples that both short-circuit in Either and complete successfully, and yeah, the foldMapM implementation is a lot faster:

[info] Benchmark                            Mode  Cnt     Score     Error  Units
[info] FoldMapABench.foldMapA              thrpt   20  2113.567 ± 137.868  ops/s
[info] FoldMapABench.foldMapM              thrpt   20  3445.172 ± 131.904  ops/s
[info] FoldMapABench.foldMapAShortCircuit  thrpt   20  5476.648 ± 289.969  ops/s
[info] FoldMapABench.foldMapMShortCircuit  thrpt   20  6699.230 ± 259.521  ops/s

@travisbrown travisbrown added this to the 2.1.0-RC1 milestone Nov 8, 2019
@LukaJCB

LukaJCB commented Nov 8, 2019

Copy link
Copy Markdown
Member

Let's update the scaladoc and get this merged :)

@mberndt123

Copy link
Copy Markdown
Contributor Author

@LukaJCB I'm not quite sure what more I'm supposed to add for scaladoc. Isn't it enough to say that this does the same thing as foldMapM?

@LukaJCB

LukaJCB commented Nov 9, 2019

Copy link
Copy Markdown
Member

Sorry had a brainfart there 😄

@LukaJCB LukaJCB left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks again @mberndt123! 👍

@LukaJCB
LukaJCB merged commit fc6580b into typelevel:master Nov 9, 2019
@mberndt123
mberndt123 deleted the mb/foldmapm branch November 9, 2019 22:05
@mberndt123 mberndt123 changed the title change constraint for foldMapM from Monad to Applicative add foldMapA as an alternative to foldMapM that only requires an Applicative rather than a monad Nov 10, 2019
@travisbrown travisbrown mentioned this pull request Nov 18, 2019
gagandeepkalra added a commit to gagandeepkalra/cats that referenced this pull request Mar 8, 2020
gagandeepkalra added a commit to gagandeepkalra/cats that referenced this pull request Mar 10, 2020
gagandeepkalra added a commit to gagandeepkalra/cats that referenced this pull request Mar 10, 2020
gagandeepkalra added a commit to gagandeepkalra/cats that referenced this pull request Mar 11, 2020
LukaJCB pushed a commit that referenced this pull request Mar 11, 2020
* added law tests for `Align[Option]`

* backported #3130 Added missing import

* backported #3130 Fixed fmt error

* backported #3130 Fixed fmt error again
gagandeepkalra added a commit to gagandeepkalra/cats that referenced this pull request Mar 11, 2020
travisbrown pushed a commit that referenced this pull request Mar 11, 2020
* backported #3130 Added foldMapA to foldable

* backported #3130 Fixed fmt error
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.

4 participants