-
-
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
Add Foldable and Traversable instances for Free #1840
Conversation
Hmm, the |
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.
It seems the build is failing because scalastyle is requiring some additional spaces.
fa.fold(f(_, lb), F.foldRight(_,lb)( (freeA,eb) => foldRight(freeA,eb)(f))) | ||
} | ||
|
||
private trait FreeTraverse[F[_]] extends Traverse[Free[F,?]] with FreeFoldable[F] { |
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.
You can override map
here to use Free#map
which is more efficient than the default map
for Traverse
(traverse
with Id
).
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.
Good point, I'll do that!
def FunctorF = implicitly | ||
} | ||
|
||
implicit def catsTraverseForFree[F[_] : Functor : Traverse]: Traverse[Free[F, ?]] = |
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.
Traverse
is a Functor
as well, so you can drop the Functor
requirement.
I think we generally prefer "explicit implicits" (is that a thing?), compared to context bounds.
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.
Hah, I was copying the style from Cofree
, but I'm happy to add explicit implicits.
implicit def catsTraverseForFree[F[_] : Functor : Traverse]: Traverse[Free[F, ?]] = | ||
new FreeTraverse[F] { | ||
def F = implicitly | ||
def FunctorF = implicitly |
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.
These def
s can be val
s.
One question I had regarded which If it's not the case that the But my gut tells me how its implemented now is correct (fwiw both styles pass the tests). |
|
||
sealed private[free] abstract class FreeInstances { | ||
|
||
implicit def catsFoldableForFree[F[_]]( |
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.
It is not really documented outside issue #1061 itself (it should probably be added to these guidelines): the implicit values in Cats are prefixed with the package.
So these implicits would be catsFreeFoldableForFree
and catsFreeTraverseForFree
.
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.
cool. Thanks, I've updated.
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 also created an issue for this: #1841
): Traverse[Free[F, ?]] = | ||
new FreeTraverse[F] { | ||
val TraversableF = traversableF | ||
val FunctorF = functorF |
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.
You might have missed my previous comment, but Traverse[F]
is also a Functor[F]
.
So you could write :
implicit def catsTraverseForFree[F[_]](implicit F: Traverse[F]): Traverse[Free[F, ?]] =
new FreeTraverse[F] {
val TraversableF = F
val FunctorF = F
}
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.
Ah, yes, I did miss that. Thanks!
Generally if a datatype implements multiple typeclasses in a hierarchy, the the implicits are overridden by a more powerful instance. For example in
So the |
FYI, I'll squash and rebase when the tests pass and I've incorporated all your feedback. Thanks again, @peterneyens !! |
Codecov Report
@@ Coverage Diff @@
## master #1840 +/- ##
==========================================
+ Coverage 94.96% 94.96% +<.01%
==========================================
Files 241 241
Lines 4173 4193 +20
Branches 106 109 +3
==========================================
+ Hits 3963 3982 +19
- Misses 210 211 +1
Continue to review full report at Codecov.
|
3c1d91c
to
98879bd
Compare
The tests passed, so I've squashed. Should be ready to merge. |
Given that these methods are recursive, we should probably use I think we can make fa.resume match {
case Left(fFreeA) =>
F.foldRight(fFreeA, lb)( (freeA, eb) =>
eb.flatMap(b => foldRight(freeA, Eval.now(b))(f))
)
case Right(a) => f(a, lb)
} |
Hmm, it seems It seems that Scalaz uses a So either we have to add something similar to |
@peterneyens ok, I'll add |
(I'll also see about making |
@peterneyens OK, so I've implemented |
I think we probably want to hide If we hide As the name suggest this.step match {
case Pure(a) => onPure(a)
case Suspend(a) => onSuspend(a)
case FlatMapped(Suspend(fa), f) => onFlatMapped((fa, f))
case _ => sys.error("FlatMapped should be right associative after step") // :(
} |
f167d9a
to
429da1f
Compare
@peterneyens ok, I've implemented |
Not sure if you know, but you can run scalastyle locally as well in sbt using |
429da1f
to
c913765
Compare
ah, hah. I was doing that before, but I wasn't prefixing it with the project (e.g. everything should be good now. |
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.
Thanks already @aaronlevin, this looks good.
I left two more comments.
@@ -55,6 +55,20 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { | |||
} | |||
|
|||
/** | |||
* A combination of step and fold. | |||
*/ | |||
final def foldStep[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.
I think we need to make this private[free]
to not expose the actual representation of Free
compared to Either[F[Free[F, A]]], A]
which is exposed by resume
and fold
.
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.
done!
fa.foldStep( | ||
a => f(a, lb), | ||
fa => F.foldRight(fa, lb)(f), | ||
{ case (fx, g) => F.foldRight(fx, lb)( (a, lbb) => foldRight(g(a), lbb)(f)) } |
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 think that lbb.flatMap(bb => foldRight(g(a), Eval.now(bb))(f))
instead of foldRight(g(a), lbb)(f)
should make this stack safe.
We should probably add a test to check this. Something like (haven't compiled this):
val n = 50000
val freeOption: Int => Free[Option, Int] = Free.pure(x)
val free = (0 to n).foldLeft(freeOption(0))((r, _) => r.flatMap(n => freeOption(n + 1)))
free.foldRight(0)((a, lb) => lb.map(_ + a)) should (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.
i've added the test!
c913765
to
360025f
Compare
@peterneyens I'm not savvy about understanding
Is this expected. |
I am sorry, it seems my comments about Feel free to revert back to your original |
360025f
to
2eb3099
Compare
@peterneyens ok, I've updated and added the |
@peterneyens I just tried with the old implementations of So I'm not convinced that example is actually testing the stack. I'm also unsure if there is a problem with the stacks. Perhaps the implementation of |
It seems you are right, not sure what I tried yesterday which let to a stack overflow. Sorry for the confusion. |
): Traverse[Free[F, ?]] = | ||
new FreeTraverse[F] { | ||
val TraversableF = traversableF | ||
val FunctorF = traversableF |
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 val FunctorF
isn't used any more 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.
fixed.
e92ce5f
to
3cdb62d
Compare
@peterneyens ok, I've just squashed everything. so I think this is good. Who can merge this? |
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.
Thanks @aaronlevin!
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.
👍
Thanks for pushing this through!
Addresses #1833 .