-
-
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
added a new foldRight lazy law, move forallLazy and existLazy laws #2817
Conversation
BTW, strictly speaking this is breaking change for |
I guess it makes sense because someone might override |
This mistake is actually incredibly easy to make. Here is a silly example: implicit val foldableSeq: Foldable[Seq] = new Foldable[Seq] {
def foldLeft[A, B](fa: Seq[A], b: B)(f: (B, A) => B) = fa.foldLeft(b)(f)
def foldRight[A, B](fa: Seq[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = fa.foldRight(lb)(f)
override def forall[A](fa: Seq[A])(p: A => Boolean) = fa.forall(p)
override def exists[A](fa: Seq[A])(p: A => Boolean) = fa.exists(p)
} |
@joroKr21 funny that's exactly what I just discovered on |
Codecov Report
@@ Coverage Diff @@
## master #2817 +/- ##
==========================================
+ Coverage 94.15% 94.21% +0.05%
==========================================
Files 368 368
Lines 6914 6944 +30
Branches 296 301 +5
==========================================
+ Hits 6510 6542 +32
+ Misses 404 402 -2
Continue to review full report at Codecov.
|
def foldRightLazy[A](fa: F[A]): Boolean = { | ||
var i = 0 | ||
F.foldRight(fa, Eval.now("empty")) { (_, _) => | ||
i += 1 |
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 use += here but not below. Any reason for the difference?
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.
no real reason except this one is written by me and the other ones are from before. Just changed the other ones to be more consistent.
@@ -21,24 +21,6 @@ trait UnorderedFoldableLaws[F[_]] { | |||
(F.isEmpty(fa) || F.exists(fa)(p)) | |||
} else true // can't test much in this case | |||
|
|||
def existsLazy[A](fa: F[A]): Boolean = { |
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.
Foldable extends UnorderedFoldable. To me, the bug here is that we weren’t calling these in UnorderedFoldable tests and that FoldableLaws were not calling through to them as we should do any time we extend a typeclass.
That seems like the better fix to me than moving the law (since we should still want these laws on UnorderedFoldable).
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 does look that way but actually the default implementations of exist
and forall
in UnorderedFoldable
do not obey these laws. These laws can only be supported in Foldable
through foldRight
, hence they should be in Foldable
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 can keep the laws in UnorderedFoldableTests
if we implement the CommutativeMonoid[Eval[Boolean]]
s directly in UnorderedFoldable
. See rossabaker@4af246d.
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.
👍 to foldRightLazy
.
We don't need to move the laws. See below.
@@ -21,24 +21,6 @@ trait UnorderedFoldableLaws[F[_]] { | |||
(F.isEmpty(fa) || F.exists(fa)(p)) | |||
} else true // can't test much in this case | |||
|
|||
def existsLazy[A](fa: F[A]): Boolean = { |
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 can keep the laws in UnorderedFoldableTests
if we implement the CommutativeMonoid[Eval[Boolean]]
s directly in UnorderedFoldable
. See rossabaker@4af246d.
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’d rather keep the laws the forall and exists are lazy on UnorderedFoldable.
That said, the worst case is already linear complexity so maybe that law is too restrictive anyway. Maybe the real mistake is forcing laziness on Foldable.
If we are going to keep a laziness law, my vote is to keep it on UnorderedFoldable, if we aren’t going to do that, I’d next prefer to remove the laziness requirement entirely. |
I think one might expect the laziness from the type signature. |
The commit I linked above keeps the lazy laws on |
@RossBaker I think the approach is good but I have a few nits:
|
Thanks for all the feedbacks. I will incorporate @RossBaker's code with @johnynek's suggestions |
I agree with @johnynek's suggestions, but if we override for |
@rossabaker https://github.com/typelevel/cats/blob/master/tests/src/test/scala/cats/tests/UnorderedFoldableSuite.scala is already setup to test some other default implemetations. This should be easy to add. |
I noticed that
foldRight
's laziness was tested throughforallLazy
andexistLazy
laws. SincefoldRight
is a type class method I think we should have the laziness law on it. In kittens we were caught by the lack of such a law.Also I noticed that although
forallLazy
andexistLazy
are defined inUnorderedFoldableLaws
, they are actually only used inFoldableTests
. Moreover, implementations inUnorderedFoldable
breaks these two laws. So I think they should probably beFoldableLaws
.Update: This actually caught a bug in NonEmptyChain whose
Foldable.foldRight
implementation is simply delegating to the native (scala std lib)foldRight
.