-
-
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
TraverseFilter (AKA Witherable) type class #1148
Conversation
* res0: List[String] = List(one, three) | ||
* }}} | ||
*/ | ||
def mapOptionA[G[_]: Applicative, A, B](fa: F[A])(f: A => G[Option[B]]): G[F[B]] |
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 guess another potential name for this would be traverseOption
.
Current coverage is 89.32%@@ master #1148 diff @@
==========================================
Files 232 238 +6
Lines 3073 3195 +122
Methods 3021 3139 +118
Messages 0 0
Branches 49 53 +4
==========================================
+ Hits 2729 2854 +125
+ Misses 344 341 -3
Partials 0 0
|
val rhs: Nested[M, N, F[C]] = fa.mapOptionA[Nested[M, N, ?], C](a => | ||
Nested(f(a).map(_.traverseM(g)))) | ||
lhs <-> rhs | ||
} |
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.
So, I haven’t tried it, but the only difference I see between your law and the Haskell one is that the Haskell uses mapOptionA
where you use traverseM
. And I think the types line up if you make that change.
More superficially, the Haskell version also swaps the names f
and g
.
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.
@sellout It's an Option[B]
that I'm calling traverseM
on. I think there's an extra layer of Option
if I try to use mapOptionA
. It seems off since I'm not actually calling mapOptionA
twice.
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 think it’s fine that it’s an Option[B]
– the type signatures of mapOptionA
and traverseM
are both F[A] =...> G[F[B]]
def mapOptionA[G[_]: Applicative, A, B](fa: F[A])(f: A => G[Option[B]]): G[F[B]]
def traverseM[G[_]: Applicative, A, B](fa: F[A])(f: A => G[F[B]]): G[F[B]]
so in this specific case, they both go Option[B] => (B => N[Option[C]]) => N[Option[C]]
. The only difference is how the Option
in N[Option[C]]
is handled – IIUC, mapOptionA
throws away the None
cases, while traverseM
flatMaps them, which doesn’t throw them away.
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.
Ah so do I need to write an instance for Option and use it in the law? I
assumed I would only need the instance for the abstract F.
On Jun 19, 2016 10:17 PM, "Greg Pfeil" notifications@github.com wrote:
In laws/src/main/scala/cats/laws/CollectLaws.scala
#1148 (comment):
- fa: F[A],
- f: A => M[Option[B]],
- g: B => N[Option[C]]
- )(implicit
M: Applicative[M],
N: Applicative[N]
- ): IsEq[Nested[M, N, F[C]]] = {
- val lhs: Nested[M, N, F[C]] = Nested(fa.mapOptionA(f).map(_.mapOptionA(g)))
- // TODO is this right and/or meaningful? I can't seem to get the types to
- // line up for the a straight port of
wither (Compose. fmap (wither f). g)
- // which is how the law is stated for Haskell's
Witherable
- val rhs: Nested[M, N, F[C]] = fa.mapOptionA[Nested[M, N, ?], C](a =>
Nested(f(a).map(_.traverseM(g))))
- lhs <-> rhs
- }
I think it’s fine that it’s an Option[B] – the type signatures of mapOptionA
and traverseM are both F[A] =...> G[F[B]]
def mapOptionA[G[]: Applicative, A, B](fa: F[A])(f: A =>
G[Option[B]]): G[F[B]]def traverseM[G[]: Applicative, A, B](fa:
F[A])(f: A => G[F[B]]): G[F[B]]
so in this specific case, they both go Option[B] => (B => N[Option[C]]) =>
N[Option[C]]. The only difference is how the Option in N[Option[C]] is
handled – IIUC, mapOptionA throws away the None cases, while traverseM
flatMaps them, which doesn’t throw them away.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/typelevel/cats/pull/1148/files/139659472e3b698dd0e392099f01155f33cbe1b5#r67630044,
or mute the thread
https://github.com/notifications/unsubscribe/AA7sCUSHu09nEV8TRexRAl7JhuoU5XJiks5qNfhMgaJpZM4I5Mbj
.
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 actually hadn’t thought about it, but yeah, it looks like that’s the case. It looks like Witherable has an instance for Maybe as well as some others.
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.
Thanks for the help @sellout. I've updated this PR to include what I think is the right representation of the law.
I renamed the type class from |
Resolves typelevel#1054. This simply removes `MonadFilter.filterM` to prevent confusion due to this `filterM` being different than that from Haskell or Scalaz. This version of `filterM` doesn't seem particularly useful to me. The only type that comes to mind for which this might be useful is `Option`, and it's pretty easy to come up with a more straightforward implementation for the `Option` case. A more general solution for `filterM` is provided by typelevel#1148, but so far there doesn't seem to be any interest in that proposal, so I don't want it to hold up resolving
Looks great to me. Thanks very much! @ceedubs 👍 |
* that can essentially have a [[traverse]] and a [[filter]] applied as a single | ||
* combined operation ([[traverseFilter]]). | ||
* | ||
* TODO ceedumbs more info |
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.
Can we add the laws:
A definition of wither must satisfy the following laws:
identity
wither (pure . Just) ≡ pure
composition
Compose . fmap (wither f) . wither g ≡ wither (Compose . fmap (wither f) . g)
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.
By which I mean, mention in the comments the laws, I see they are encoded below in the tests.
This looks really nice. 👍 |
@@ -55,6 +55,12 @@ import simulacrum.typeclass | |||
val G = Traverse[G] | |||
} | |||
|
|||
def composeFilter[G[_]: TraverseFilter]: TraverseFilter[λ[α => F[G[α]]]] = |
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.
Are people happy with this? Since we only need a Traverse
for F
but a TraverseFilter
for G
, and there is a separate compose
when G
only has Traverse
, it doesn't quite fit the common pattern.
@kailuowang @johnynek thanks for the reviews! Since you've shown support for this change, I've fleshed it out a bit. Would you mind taking a second look? I left a couple minor comments seeking feedback, but I think they are for things that could probably be addressed in a later PR. The one item that I'm still a little uneasy about is this (from the original PR description):
|
I'm closing this in favor of #1225, where I've cleaned up the git history a bit and added |
This is not meant to be merged in its current form.
This is a proof of concept for adding a
TraverseFilter
type class, which is a port of Haskell's Data.Witherable. I'm not set on any of the naming, so feel free to offer other recommendations.Would people be interested in something like this? Some things that I like about it are:
filterM
(actually a more general version of it) filterM broken? #1054.collect
andmapFilter
(akamapMaybe
) methods collect and mapMaybe for MonadFilter? #842. Sincecollect
is often used on standard library collections, I think this is a nice feature to support.traverseFilter
supports operations that essentially do afilter
and atraverse
at the same time, which is something that you can't get from eitherTraverse
orMonadFilter
.Some things that I'm not so sure about:
MonadFilter
here. For example,filter
andcollect
can be defined for both. I'm not sure if there should be a closer relationship between the two. Also syntax enrichment for methods likefilter
could potentially be ambiguous between the two types (though I don't know how much of a problem this will be in practice as most concrete types that supportfilter
will have it defined on them).Compose . fmap (wither f) . wither g ≡ wither (Compose . fmap (wither f) . g)
, but I can't get the types to line up on that. I don't know Haskell very well and struggle with point free style, so I'm probably losing something in translation. I have a different version of the right hand side of the composition law that compiles, but it seems much less direct than the haskell version, and I don't know know if it's meaningfully distinct from the left hand side. I'm hoping someone who knows Haskell well (such as @aaronlevin or @sellout) and/or someone who knows laws well (such as @fthomas) can weigh in on this.If people want to go forward with this I need to provide more instances, tests, and documentation.