Skip to content

Add UnorderedFoldable and UnorderedTraverse - #1981

Merged
kailuowang merged 41 commits into
typelevel:masterfrom
LukaJCB:add-unordered-classes
Nov 25, 2017
Merged

Add UnorderedFoldable and UnorderedTraverse #1981
kailuowang merged 41 commits into
typelevel:masterfrom
LukaJCB:add-unordered-classes

Conversation

@LukaJCB

@LukaJCB LukaJCB commented Oct 19, 2017

Copy link
Copy Markdown
Member

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

codecov-io commented Oct 19, 2017

Copy link
Copy Markdown

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.

@LukaJCB
LukaJCB force-pushed the add-unordered-classes branch from 81feb52 to 74b0228 Compare October 20, 2017 00:53
@kailuowang

Copy link
Copy Markdown
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

LukaJCB commented Oct 20, 2017

Copy link
Copy Markdown
Member Author

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

@kailuowang

Copy link
Copy Markdown
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.

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

kailuowang commented Nov 1, 2017

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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! :)

@hrhino hrhino Nov 10, 2017

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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