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.
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.
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.
@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.
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.
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.
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.
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.
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.
By which I mean, mention in the comments the laws, I see they are encoded below in the tests.
|
This looks really nice. 👍 |
| val G = Traverse[G] | ||
| } | ||
|
|
||
| def composeFilter[G[_]: TraverseFilter]: TraverseFilter[λ[α => F[G[α]]]] = |
There was a problem hiding this comment.
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
TraverseFiltertype 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.collectandmapFilter(akamapMaybe) methods collect and mapMaybe for MonadFilter? #842. Sincecollectis often used on standard library collections, I think this is a nice feature to support.traverseFiltersupports operations that essentially do afilterand atraverseat the same time, which is something that you can't get from eitherTraverseorMonadFilter.Some things that I'm not so sure about:
MonadFilterhere. For example,filterandcollectcan be defined for both. I'm not sure if there should be a closer relationship between the two. Also syntax enrichment for methods likefiltercould 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 supportfilterwill 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.