-
-
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 UnorderedFoldable and UnorderedTraverse #1981
Changes from 29 commits
0ea4ac0
0cd4eb9
f9a239a
965f9c2
d3a803a
1ab4f47
2164b51
7f1f6c7
75a656a
69c6374
fa8a731
2aeddd7
aa49de9
d308cf7
148c113
0ffbc55
23cb589
e0069a8
376a5c6
d673655
8666c64
74b0228
dfa8bd3
903694d
821424d
a1cdf2d
cdc6586
3e705d4
5f4d41f
cd2e1f5
5d9f0f7
f70fe6f
04722f0
9568933
9666a0c
23a6ca1
d2037e6
11b397c
89cab23
e2984b4
ea38cd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package cats | ||
|
||
import cats.kernel.CommutativeMonoid | ||
import simulacrum.typeclass | ||
import cats.instances.set._ | ||
/** | ||
* `UnorderedFoldable` is like a `Foldable` for unordered containers. | ||
*/ | ||
@typeclass trait UnorderedFoldable[F[_]] { | ||
|
||
def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: A => B): B | ||
|
||
def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = | ||
unorderedFoldMap(fa)(identity) | ||
|
||
def toSet[A](fa: F[A]): Set[A] = | ||
unorderedFoldMap(fa)(a => Set(a)) | ||
|
||
/** | ||
* Returns true if there are no elements. Otherwise false. | ||
*/ | ||
def isEmpty[A](fa: F[A]): Boolean = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be implemented with foldMap (map to true, do the commutative “or” monoid |
||
exists(fa)(Function.const(true)) | ||
|
||
def nonEmpty[A](fa: F[A]): Boolean = | ||
!isEmpty(fa) | ||
|
||
/** | ||
* Check whether at least one element satisfies the predicate. | ||
* | ||
* If there are no elements, the result is `false`. | ||
*/ | ||
def exists[A](fa: F[A])(p: A => Boolean): Boolean = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be implemented with unorderedFoldMap |
||
unorderedFoldMap(fa)(p)(UnorderedFoldable.orMonoid) | ||
|
||
/** | ||
* Check whether all elements satisfy the predicate. | ||
* | ||
* If there are no elements, the result is `true`. | ||
*/ | ||
def forall[A](fa: F[A])(p: A => Boolean): Boolean = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be implemented with foldMap using and monoid. |
||
unorderedFoldMap(fa)(p)(UnorderedFoldable.andMonoid) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could implement the |
||
|
||
object UnorderedFoldable { | ||
private val orMonoid: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] { | ||
val empty: Boolean = false | ||
|
||
def combine(x: Boolean, y: Boolean): Boolean = x || y | ||
} | ||
|
||
private val andMonoid: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] { | ||
val empty: Boolean = true | ||
|
||
def combine(x: Boolean, y: Boolean): Boolean = x && y | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package cats | ||
|
||
import simulacrum.typeclass | ||
|
||
/** | ||
* `UnorderedTraverse` is like a `Traverse` for unordered containers. In addition to the traverse and sequence | ||
* methods it provides nonEmptyTraverse and nonEmptySequence methods which require an `Apply` instance instead of `Applicative`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line of docs seems inaccurate? |
||
*/ | ||
@typeclass trait UnorderedTraverse[F[_]] extends UnorderedFoldable[F] { | ||
def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: F[A])(f: A => G[B]): G[F[B]] | ||
|
||
def unorderedSequence[G[_]: CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] = | ||
unorderedTraverse(fga)(identity) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package cats | ||
package laws | ||
|
||
import cats.implicits._ | ||
import cats.kernel.CommutativeMonoid | ||
|
||
trait UnorderedFoldableLaws[F[_]] { | ||
implicit def F: UnorderedFoldable[F] | ||
|
||
def unorderedFoldConsistentWithUnorderedFoldMap[A: CommutativeMonoid](fa: F[A]): IsEq[A] = | ||
F.unorderedFoldMap(fa)(identity) <-> F.unorderedFold(fa) | ||
|
||
|
||
|
||
def forallConsistentWithExists[A](fa: F[A], p: A => Boolean): Boolean = { | ||
if (F.forall(fa)(p)) { | ||
val negationExists = F.exists(fa)(a => !(p(a))) | ||
|
||
// if p is true for all elements, then there cannot be an element for which | ||
// it does not hold. | ||
!negationExists && | ||
// if p is true for all elements, then either there must be no elements | ||
// or there must exist an element for which it is true. | ||
(F.isEmpty(fa) || F.exists(fa)(p)) | ||
} else true // can't test much in this case | ||
} | ||
|
||
/** | ||
* If `F[A]` is empty, forall must return true. | ||
*/ | ||
def forallEmpty[A](fa: F[A], p: A => Boolean): Boolean = { | ||
!F.isEmpty(fa) || F.forall(fa)(p) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could it be made to be such that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think lazyness would require There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe you can do it with def exists[A](fa: F[A])(p: A => Boolean): Boolean =
foldMapUnordered(fa)(a => Eval.later(p(a)))(new CommutativeMonoid[Eval[Boolean]] {
def empty = Eval.False
def combine(x: Eval[Boolean], y: Eval[Boolean]): Eval[Boolean] =
x.flatMap(xv => if (xv) Eval.True else y)
}).value and dually for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're absolutely right, this is great! Thank you so much! |
||
|
||
def toSetRef[A](fa: F[A]): IsEq[Set[A]] = | ||
F.unorderedFoldMap(fa)(a => Set(a)) <-> F.toSet(fa) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could be val lst1 = F.unorderedFoldMap(fa)(a => List(a))
val lst2 = F.toList(fa)
def contains[A: Eq](a: A): Boolean = lst1.exists(Eq[A].eqv(_, a))
val allCont = list2.map(contains)
val allTrue = lst.map(_ => true)
allCont <-> allTrue In this way, we have a law that only needs Eq, not unusual, and the typeclass does not require any Order or Hash. |
||
|
||
def nonEmptyRef[A](fa: F[A]): IsEq[Boolean] = | ||
F.nonEmpty(fa) <-> !F.isEmpty(fa) | ||
|
||
} | ||
|
||
object UnorderedFoldableLaws { | ||
def apply[F[_]](implicit ev: UnorderedFoldable[F]): UnorderedFoldableLaws[F] = | ||
new UnorderedFoldableLaws[F] { def F: UnorderedFoldable[F] = ev } | ||
} |
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 can be implemented with foldMap
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 wonder if actually we should take an Order here. This is using universal hashing and i hate to bake that into a new typeclass.
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.
If we take an Order we could make it a
SortedSet
, not sure if that really makes sense. I agree with your sentiment, but not sure how to deal with it exactly. Maybe take aHash[A]
? I dunno.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 agree that having
UnorderedFoldable
taking aOrder
doesn't make much sense.The whole point of this PR is to support some principled functionalities for
Set
andMap
, which are based off universal hashing I am not sure if we can get away from it.If we really want
UndorderedFoldable
as a principled abstraction, we should probably only support theSet
andMap
in Dogs.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 could make this
toList
with the only law being aboutcontains
andEq[A]
. It would beO(N^2)
to check the law, but that is probably fine.I just hate sneaking universal hashing or universal equality in.
I don't see the use or
Order[A]
as not making sense, since it is the collection that is unordered here, not the typeA
. So, I don't want to get those two confused.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.
+1 on this solution.
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 not sure
toList
really makes sense, given the fact that we only want to fold using aCommutativeMonoid
whichList
clearly is not. Maybe we just get rid of this method all together? I really wish we had aHashSet
withcats.kernel.Hash
.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 agree. At the very least this is not stable enough to be included in 1.0.0. I propose we remove this and wrap this PR up.