-
-
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
foldLeftM
without Free
.
#1117
foldLeftM
without Free
.
#1117
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1117 +/- ##
==========================================
- Coverage 93.99% 93.98% -0.02%
==========================================
Files 249 249
Lines 4131 4140 +9
Branches 158 160 +2
==========================================
+ Hits 3883 3891 +8
- Misses 248 249 +1
Continue to review full report at Codecov.
|
@TomasMikula thanks, this does seem quite a bit simpler than the |
This approach basically gives an Iterator-like/Stream-like interface to (any) |
@TomasMikula I like this. I'd be interested in moving it into core somewhere where we could use it in the default implementation of |
cc @non since he and I have talked about |
Perhaps we need better naming for |
I don't like the Eval.value calls, but I don't know of a way around them. The unit tests suggest that they aren't a problem due to the nature of `Free.runTailRec` and `FlatMapRec.tailRecM`.
test(".foldLeftM") { | ||
// you can see .foldLeftM traversing the entire structure by | ||
// changing the constant argument to .take and observing the time | ||
// this test takes. |
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.
This comment should no longer be true, should it?
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.
Right, it became obsolete earlier, though. See my comment. The whole test case is redundant in favor of the one below.
@TomasMikula I agree. I think we are going to likely end up having several methods that have a |
F.foldRight[A, Source[A]](fa, Now(Source.empty[A]))((a, evalSrc) => | ||
Now(cons(a, evalSrc)) | ||
).value | ||
} |
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.
This is probably me prematurely optimizing, but I'd be tempted to do a couple things:
- Make
empty
reuse a singleton either by makingSource
covariant or by giving it adef widen[AA >: A]: Source[AA] = this.asInstanceOf[Source[AA]]
method. - Possibly store a value of
Now(Source.empty)
around for reuse byfromFoldable
.
@ceedubs I made the changes, except renaming. I like the |
@TomasMikula I was thinking about renaming the current |
@ceedubs Makes sense. My concern was just whether |
How about now? |
👍 thanks @TomasMikula! It might be nice to have the |
@TomasMikula I am sorry that this fell through the cracks, any interest in resolving the conflicts? It looks like that we are close to merging. If you don't have time I'd be happy to continue it through. |
*/ | ||
def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = | ||
def foldLeftM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = |
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.
we don't need this now, right?
* have a more efficient implementation than the default one | ||
* in terms of `foldRight`. | ||
*/ | ||
def foldLeftMRec[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: MonadRec[G]): G[B] = { |
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.
can we rename this foldLeftM
or just stick with foldM
. I'm not sure we should be changing this name this late in the game, although I agree foldLeftM
is a better name.
Maybe add a deprecated final foldM
that redirects to foldLeftM
?
ab7d173
to
e6b28b4
Compare
How about now? |
val NowEmpty: Eval[Source[Nothing]] = Now(Empty) | ||
|
||
def cons[A](a: A, src: Eval[Source[A]]): Source[A] = new Source[A] { | ||
def uncons = Some((a, src.value)) |
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'm a little nervous about these calls to value. Can we add a comment about the stack safety here? I think the answer is that we can't recursively call .value in an Eval and we should never be in an Eval when we call uncons, is that right?
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.
The presence of Eval
in this case actually just obstructs the understanding, IMO. The src
parameter of cons
could just be by-name (the evaluation of .value
doesn't need any trampolining built into Eval
). However, since foldRight
already uses Eval
, I didn't want to mix both by-name and Eval
.
A clearer definition of Source
would be
type Source[A] = () => Option[(A, Source[A])]
except that recursive type aliases are not allowed. I guess I could make it an AnyVal
, though. EDIT: Nope, because of scala/bug#9600.
* in terms of `foldRight`. | ||
*/ | ||
def foldLeftM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = { | ||
val src = Foldable.Source.fromFoldable(fa)(self) |
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.
Kind of a bummer that is someone has override on foldM they will have two different folds. Maybe one should be final and a pointer to the other?
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.
True.
I realized that in case of short-circuited fold, it evaluates 1 more element than necessary. Maybe I should fix that... EDIT: Note that because of the signature of def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] specifically the part |
👍 |
merged with 3 sign-offs |
assert(res == Left(100000)) | ||
} | ||
|
||
test(".foldLeftM short-circuiting optimality") { |
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.
This test fails if we remove Foldable.iteratorFoldM
, because it actually no longer tests the default foldM
implementation from Foldable
after #1414 which overrides foldLeftM
in the instance of Stream
using iteratorFoldM
. This test fails when using the default foldM
because, if I am not mistaken, the new foldM
still have to evaluate one element after the stop. Also these two tests are now redundant with the test("Foldable[Stream]") above. I will fix this in a PR removing iteratorFoldM
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.
Huh, I didn't notice that I was testing some other code 😃. I suppose changing the signature of
def uncons: Option[(A, Source[A])]
to
def uncons: Option[(A, Eval[Source[A]])]
and doing the necessary changes would make it pass.
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.
right, I updated #1740
This is an implementation of
foldLeftM
without usingFree
. Instead, it introduces an auxiliary structureand a conversion from any foldable
F[A]
toSource[A]
, which provides lazy access to elements ofF[A]
.It still uses
foldRight
, but I think there is a better separation of concerns here, becauseG
is not involved in thefoldRight
at all.I didn't bother to move this out ofI moved the implementation toFree.scala
, butFree
is not used here at all.Foldable
.Funny enough, if I make
Source
extendAnyVal
, it crashes the compiler with aStackOverflowError
(SI-9600).