Skip to content
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

Merged
merged 41 commits into from
Nov 25, 2017
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0ea4ac0
Add CommutativeApply and Applicative and an instance for Validated[Co…
Sep 24, 2017
0cd4eb9
Make Future a CommutativeApplicative
Sep 28, 2017
f9a239a
Revert "Make Future a CommutativeApplicative"
Sep 28, 2017
965f9c2
Add unordered traversal for Sets
Sep 30, 2017
d3a803a
Test commutativeApply instances + Nested + Tuple2K instances
Sep 30, 2017
1ab4f47
Add unordered traversal for Map
Oct 2, 2017
2164b51
Fix priority for Nested instances
Oct 2, 2017
7f1f6c7
Fix priority for Tuple2K instances
Oct 2, 2017
75a656a
Merge branch 'master' into add-commutative-apply
Oct 3, 2017
69c6374
Merge branch 'master' into add-commutative-apply
Oct 14, 2017
fa8a731
Move methods to typeclass
Oct 14, 2017
2aeddd7
Deduplicate Applicative instance
Oct 17, 2017
aa49de9
Try out new typeclasses
Oct 17, 2017
d308cf7
Add UnorderedFoldable and UnorderedTraverse and move traversal functi…
Oct 18, 2017
148c113
Merge branch 'master' into add-commutative-apply
Oct 18, 2017
0ffbc55
Remove Set from Foldable docs
Oct 19, 2017
23cb589
Add extra ref law
Oct 19, 2017
e0069a8
Merge branch 'master' into add-commutative-apply
Oct 19, 2017
376a5c6
Revert "Add UnorderedFoldable and UnorderedTraverse and move traversa…
Oct 19, 2017
d673655
Revert "Remove Set from Foldable docs"
Oct 19, 2017
8666c64
Add Unordered type classes
Oct 19, 2017
74b0228
Make UnorderedFoldable and Traverse super classes
Oct 19, 2017
dfa8bd3
Make unorderedFoldMap the main method
Oct 20, 2017
903694d
define isEmpty in terms of exists
Oct 20, 2017
821424d
Merge branch 'master' into add-unordered-classes
Oct 20, 2017
a1cdf2d
Merge branch 'master' into add-unordered-classes
Oct 27, 2017
cdc6586
Merge branch 'add-unordered-classes' of https://github.com/LukaJCB/ca…
Oct 27, 2017
3e705d4
Merge branch 'master' into add-unordered-classes
Oct 31, 2017
5f4d41f
Add mention to unorderedFoldable in foldable scaladoc
Oct 31, 2017
cd2e1f5
Merge branch 'master' into add-unordered-classes
kailuowang Nov 9, 2017
5d9f0f7
Add size to UnorderedFoldable
Nov 10, 2017
f70fe6f
Merge branch 'add-unordered-classes' of https://github.com/LukaJCB/ca…
Nov 10, 2017
04722f0
Made exists and forall sufficiently lazy
Nov 14, 2017
9568933
Add Mima exceptions
Nov 14, 2017
9666a0c
Merge branch 'master' into add-unordered-classes
Nov 14, 2017
23a6ca1
Merge branch 'master' into add-unordered-classes
Nov 16, 2017
d2037e6
Merge branch 'master' into add-unordered-classes
kailuowang Nov 21, 2017
11b397c
Fix merging mistake
Nov 22, 2017
89cab23
Remove toSet
Nov 24, 2017
e2984b4
Merge branch 'add-unordered-classes' of https://github.com/LukaJCB/ca…
Nov 24, 2017
ea38cd6
Remove unused imports
Nov 24, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cats
import scala.collection.mutable
import cats.instances.either._
import cats.instances.long._
import cats.kernel.CommutativeMonoid
import simulacrum.typeclass

/**
Expand All @@ -15,6 +16,7 @@ import simulacrum.typeclass
* `Foldable[_]` instance.
*
* Instances of Foldable should be ordered collections to allow for consistent folding.
* Use the `UnorderedFoldable` type class if you want to fold over unordered collections.
*
* Foldable[F] is implemented in terms of two basic methods:
*
Expand All @@ -26,7 +28,7 @@ import simulacrum.typeclass
*
* See: [[http://www.cs.nott.ac.uk/~pszgmh/fold.pdf A tutorial on the universality and expressiveness of fold]]
*/
@typeclass trait Foldable[F[_]] { self =>
@typeclass trait Foldable[F[_]] extends UnorderedFoldable[F] { self =>

/**
* Left associative fold on 'F' using the function 'f'.
Expand Down Expand Up @@ -54,6 +56,7 @@ import simulacrum.typeclass
*/
def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B


/**
* Right associative lazy fold on `F` using the folding function 'f'.
*
Expand Down Expand Up @@ -360,7 +363,7 @@ import simulacrum.typeclass
*
* If there are no elements, the result is `false`.
*/
def exists[A](fa: F[A])(p: A => Boolean): Boolean =
override def exists[A](fa: F[A])(p: A => Boolean): Boolean =
foldRight(fa, Eval.False) { (a, lb) =>
if (p(a)) Eval.True else lb
}.value
Expand All @@ -370,7 +373,7 @@ import simulacrum.typeclass
*
* If there are no elements, the result is `true`.
*/
def forall[A](fa: F[A])(p: A => Boolean): Boolean =
override def forall[A](fa: F[A])(p: A => Boolean): Boolean =
foldRight(fa, Eval.True) { (a, lb) =>
if (p(a)) lb else Eval.False
}.value
Expand Down Expand Up @@ -509,10 +512,10 @@ import simulacrum.typeclass
/**
* Returns true if there are no elements. Otherwise false.
*/
def isEmpty[A](fa: F[A]): Boolean =
override def isEmpty[A](fa: F[A]): Boolean =
foldRight(fa, Eval.True)((_, _) => Eval.False).value

def nonEmpty[A](fa: F[A]): Boolean =
override def nonEmpty[A](fa: F[A]): Boolean =
!isEmpty(fa)

/**
Expand Down Expand Up @@ -551,6 +554,11 @@ import simulacrum.typeclass
val F = self
val G = Foldable[G]
}

override def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = fold(fa)

override def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: (A) => B): B =
foldMap(fa)(f)
}

object Foldable {
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/scala/cats/Traverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import simulacrum.typeclass
*
* See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]]
*/
@typeclass trait Traverse[F[_]] extends Functor[F] with Foldable[F] { self =>
@typeclass trait Traverse[F[_]] extends Functor[F] with Foldable[F] with UnorderedTraverse[F] { self =>

/**
* Given a function which returns a G effect, thread this effect
Expand Down Expand Up @@ -124,4 +124,10 @@ import simulacrum.typeclass
*/
def zipWithIndex[A](fa: F[A]): F[(A, Int)] =
mapWithIndex(fa)((a, i) => (a, i))

override def unorderedTraverse[G[_] : CommutativeApplicative, A, B](sa: F[A])(f: (A) => G[B]): G[F[B]] =
traverse(sa)(f)

override def unorderedSequence[G[_] : CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] =
sequence(fga)
}
58 changes: 58 additions & 0 deletions core/src/main/scala/cats/UnorderedFoldable.scala
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] =
Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Member Author

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 a Hash[A]? I dunno.

Copy link
Contributor

@kailuowang kailuowang Nov 14, 2017

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 a Order doesn't make much sense.
The whole point of this PR is to support some principled functionalities for Set and Map, 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 the Set and Map in Dogs.

Copy link
Contributor

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 about contains and Eq[A]. It would be O(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 type A. So, I don't want to get those two confused.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 on this solution.

Copy link
Member Author

@LukaJCB LukaJCB Nov 24, 2017

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 a CommutativeMonoid which List clearly is not. Maybe we just get rid of this method all together? I really wish we had a HashSet with cats.kernel.Hash.

Copy link
Contributor

@kailuowang kailuowang Nov 24, 2017

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.

unorderedFoldMap(fa)(a => Set(a))

/**
* Returns true if there are no elements. Otherwise false.
*/
def isEmpty[A](fa: F[A]): Boolean =
Copy link
Contributor

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 (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 =
Copy link
Contributor

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 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 =
Copy link
Contributor

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 using and monoid.

unorderedFoldMap(fa)(p)(UnorderedFoldable.andMonoid)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This could implement the size method from Foldable as well, I think.


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
}

}
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/UnorderedTraverse.scala
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`.
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
}
25 changes: 23 additions & 2 deletions core/src/main/scala/cats/instances/map.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package instances

import cats.kernel.CommutativeMonoid

import scala.annotation.tailrec

trait MapInstances extends cats.kernel.instances.MapInstances {
Expand All @@ -14,8 +16,16 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
}

// scalastyle:off method.length
implicit def catsStdInstancesForMap[K]: FlatMap[Map[K, ?]] =
new FlatMap[Map[K, ?]] {
implicit def catsStdInstancesForMap[K]: UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] =
new UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] {

def unorderedTraverse[G[_], A, B](fa: Map[K, A])(f: A => G[B])(implicit G: CommutativeApplicative[G]): G[Map[K, B]] = {
val gba: Eval[G[Map[K, B]]] = Always(G.pure(Map.empty))
val gbb = Foldable.iterateRight(fa, gba){ (kv, lbuf) =>
G.map2Eval(f(kv._2), lbuf)({ (b, buf) => buf + (kv._1 -> b)})
}.value
G.map(gbb)(_.toMap)
}

override def map[A, B](fa: Map[K, A])(f: A => B): Map[K, B] =
fa.map { case (k, a) => (k, f(a)) }
Expand All @@ -39,6 +49,9 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
def flatMap[A, B](fa: Map[K, A])(f: (A) => Map[K, B]): Map[K, B] =
fa.flatMap { case (k, a) => f(a).get(k).map((k, _)) }

def unorderedFoldMap[A, B: CommutativeMonoid](fa: Map[K, A])(f: (A) => B) =
fa.foldLeft(Monoid[B].empty){ case (b, (k, a)) => Monoid[B].combine(b, f(a)) }

def tailRecM[A, B](a: A)(f: A => Map[K, Either[A, B]]): Map[K, B] = {
val bldr = Map.newBuilder[K, B]

Expand All @@ -57,6 +70,14 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
f(a).foreach { case (k, a) => descend(k, a) }
bldr.result
}


override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty

override def unorderedFold[A](fa: Map[K, A])(implicit A: CommutativeMonoid[A]): A =
A.combineAll(fa.values)

override def toSet[A](fa: Map[K, A]): Set[A] = fa.values.toSet
}
// scalastyle:on method.length
}
27 changes: 25 additions & 2 deletions core/src/main/scala/cats/instances/set.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
package cats
package instances

import cats.kernel.CommutativeMonoid

import cats.syntax.show._

trait SetInstances extends cats.kernel.instances.SetInstances {

implicit val catsStdInstancesForSet: MonoidK[Set] =
new MonoidK[Set] {
implicit val catsStdInstancesForSet: UnorderedTraverse[Set] with MonoidK[Set] =
new UnorderedTraverse[Set] with MonoidK[Set] {

def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: Set[A])(f: A => G[B]): G[Set[B]] =
sa.foldLeft(Applicative[G].pure(Set.empty[B])) { (acc, a) =>
Apply[G].map2(acc, f(a))(_ + _)
}

override def unorderedSequence[G[_]: CommutativeApplicative, A](sa: Set[G[A]]): G[Set[A]] =
sa.foldLeft(Applicative[G].pure(Set.empty[A])) { (acc, a) =>
Apply[G].map2(acc, a)(_ + _)
}

def empty[A]: Set[A] = Set.empty[A]

def combineK[A](x: Set[A], y: Set[A]): Set[A] = x | y

def unorderedFoldMap[A, B](fa: Set[A])(f: A => B)(implicit B: CommutativeMonoid[B]): B =
fa.foldLeft(B.empty)((b, a) => B.combine(f(a), b))

override def unorderedFold[A](fa: Set[A])(implicit A: CommutativeMonoid[A]): A = A.combineAll(fa)

override def toSet[A](fa: Set[A]): Set[A] = fa

override def forall[A](fa: Set[A])(p: A => Boolean): Boolean =
fa.forall(p)

override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty
}

implicit def catsStdShowForSet[A:Show]: Show[Set[A]] = new Show[Set[A]] {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/foldable.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package syntax

trait FoldableSyntax extends Foldable.ToFoldableOps {
trait FoldableSyntax extends Foldable.ToFoldableOps with UnorderedFoldable.ToUnorderedFoldableOps {
implicit final def catsSyntaxNestedFoldable[F[_]: Foldable, G[_], A](fga: F[G[A]]): NestedFoldableOps[F, G, A] =
new NestedFoldableOps[F, G, A](fga)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/tut/typeclasses/foldable.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ scaladoc: "#cats.Foldable"
Foldable type class instances can be defined for data structures that can be
folded to a summary value.

In the case of a collection (such as `List` or `Set`), these methods will fold
In the case of a collection (such as `List` or `Vector`), these methods will fold
together (combine) the values contained in the collection to produce a single
result. Most collection types have `foldLeft` methods, which will usually be
used by the associated `Foldable[_]` instance.
Expand Down
33 changes: 3 additions & 30 deletions laws/src/main/scala/cats/laws/FoldableLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import cats.implicits._

import scala.collection.mutable

trait FoldableLaws[F[_]] {
trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] {
implicit def F: Foldable[F]

def leftFoldConsistentWithFoldMap[A, B](
Expand All @@ -26,13 +26,11 @@ trait FoldableLaws[F[_]] {
fa.foldMap(f) <-> fa.foldRight(Later(M.empty))((a, lb) => lb.map(f(a) |+| _)).value
}

def existsConsistentWithFind[A](
fa: F[A],
p: A => Boolean
): Boolean = {
def existsConsistentWithFind[A](fa: F[A], p: A => Boolean): Boolean = {
F.exists(fa)(p) == F.find(fa)(p).isDefined
}


def existsLazy[A](fa: F[A]): Boolean = {
var i = 0
F.exists(fa){ _ =>
Expand All @@ -51,31 +49,6 @@ trait FoldableLaws[F[_]] {
i == (if (F.isEmpty(fa)) 0 else 1)
}

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)
}

/**
* Monadic folding with identity monad is analogous to `foldLeft`.
Expand Down
2 changes: 1 addition & 1 deletion laws/src/main/scala/cats/laws/TraverseLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cats.data.{Const, Nested, State, StateT}
import cats.syntax.traverse._
import cats.syntax.foldable._

trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] {
trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] with UnorderedTraverseLaws[F] {
implicit override def F: Traverse[F]

def traverseIdentity[A, B](fa: F[A], f: A => B): IsEq[F[B]] = {
Expand Down
46 changes: 46 additions & 0 deletions laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could it be made to be such that existsLazy and forallLazy are laws for UnorderedFoldable and not just Foldable?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think lazyness would require foldRight, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe you can do it with Eval: see my duplicative attempt at this type class.

  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 forall.

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
if we have an Order[A]. So we use an Order to check the law more quickly, or we could


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 }
}
Loading