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

Conversation

LukaJCB
Copy link
Member

@LukaJCB LukaJCB commented Oct 19, 2017

As discussed in #1927, together with #1972 and #1966 this should fix #1831.
In this PR, I remove the Traverse and Foldable instances from Map and Set and replace them with UnorderedTraverse and UnorderedFoldable. I think we could let Foldable and Traverse extend these.

This PR depends on #1927.

@codecov-io
Copy link

codecov-io commented Oct 19, 2017

Codecov Report

Merging #1981 into master will decrease coverage by 0.1%.
The diff coverage is 90.81%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1981      +/-   ##
==========================================
- Coverage   95.25%   95.14%   -0.11%     
==========================================
  Files         305      311       +6     
  Lines        5179     5252      +73     
  Branches      127      131       +4     
==========================================
+ Hits         4933     4997      +64     
- Misses        246      255       +9
Impacted Files Coverage Δ
...a/cats/laws/discipline/NonEmptyTraverseTests.scala 100% <ø> (ø) ⬆️
laws/src/main/scala/cats/laws/TraverseLaws.scala 92.3% <ø> (ø) ⬆️
core/src/main/scala/cats/syntax/foldable.scala 100% <ø> (ø) ⬆️
laws/src/main/scala/cats/laws/FoldableLaws.scala 100% <ø> (ø) ⬆️
...in/scala/cats/laws/discipline/ReducibleTests.scala 100% <ø> (ø) ⬆️
...ain/scala/cats/laws/discipline/TraverseTests.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/instances/set.scala 100% <100%> (ø) ⬆️
.../cats/laws/discipline/UnorderedTraverseTests.scala 100% <100%> (ø)
core/src/main/scala/cats/Foldable.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/instances/map.scala 92.59% <100%> (+2.59%) ⬆️
... and 13 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9020258...ea38cd6. Read the comment docs.

@kailuowang
Copy link
Contributor

I like the idea of introducing these two new type classes.
I am not sure they should be introduced to the Traverse/Foldable hierarchy. It's not clear to me that for Traverse and Foldable and their instances, what benefit them for inheriting UnorderedFoldable and UnorderedTraverse. They have to override most methods and they got two additional unorderred methods that are not needed by their instances. Is there a concrete example in which such inheritance arrangement would be beneficial?

@LukaJCB
Copy link
Member Author

LukaJCB commented Oct 20, 2017

Good question! I don't think there's a huge benefit apart from deduplication.

@kailuowang
Copy link
Contributor

You probably guessed what I would say next. They are useful but

  1. not sure how useful as an abstraction to be included in core. If not we could just add them as Map and Set syntax extension.
  2. not sure if, as abstractions, they are mature enough to be locked down in RC1.

Sorry, I am biased towards limiting scope in 1.0.

@kailuowang
Copy link
Contributor

Can we make these two separate from the Traverse and Foldable. The way I see it they are parallel rather than hierarchical. Traverse and Foldable are for ordered data types, while these two are for unordered ones.

johnynek
johnynek previously approved these changes Oct 31, 2017
@LukaJCB LukaJCB added this to the 1.0.0 milestone Nov 1, 2017
@kailuowang
Copy link
Contributor

kailuowang commented Nov 1, 2017

Can we rebase master? I am curious if this one is bin compat with 1.0.0-RC1, Also I still feel pretty strongly that we should not have Traverse and Foldable inherit from them.

Update: I can see benefits of having UnorderedTraverse inheriting Traverse now.


/**
* `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?

*/
def forall[A](fa: F[A])(p: A => Boolean): Boolean =
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.

*/
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!


import cats.data.Nested

trait UnorderedTraverseLaws[F[_]] extends UnorderedFoldableLaws[F] {
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/should extend FunctorLaws[F] and assert some things about the compatibility of UnorderedTraverse and Functor; for instance, I would expect unorderedSequence to be unorderedTraverse on identity (which assumes that Eq for these instances respects unorderedness if it's not also a Traverse, I think)

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmm, I'm not sure. That would imply that Set would be a valid Functor which I believe it is not. We could add a function like unorderedMap that's just unorderedTraverse with Id.

Thanks for your review by the way, it's been very helpful! :)

Copy link
Contributor

@hrhino hrhino Nov 10, 2017

Choose a reason for hiding this comment

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

No, you're right; I got a little bit overenthusiastic about drawing these parallels.

I think unorderedMap is kinda dubious, because the difference isn't even lack of order (which is unobservable in Functor#map already!), but that the operation can "drop" values, losing fusion, and therefore isn't a (categorical) Functor. I don't know what laws for unorderedMap would be, other than "it's the same as traversing with Id".

}

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

kailuowang
kailuowang previously approved these changes Nov 24, 2017
@kailuowang kailuowang merged commit d4a3cf5 into typelevel:master Nov 25, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Foldable.toList potentially inconsistent for Map
6 participants