-
-
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
Add mapOrKeep
to Functor
#4582
Add mapOrKeep
to Functor
#4582
Conversation
Thank you for the contribution! fa.map {
case `cond_a1` => calc_a1
...
case `cond_aN` => calc_aN
case default => default
} into something like this: fa.alter {
case `cond_a1` => calc_a1
...
case `cond_aN` => calc_aN
} So basically it should help to avoid the "default" case, would it be an accurate assumption? I feel that since it is basically a Sorry for bikeshedding but I feel that names like |
yep, that's correct
Don't be sorry :) you concern makes sense. I also wanted to use something like |
0c12d73
to
6cc5ddf
Compare
ab4b7af
to
a041a94
Compare
The name My gut says that the correct name here is |
Just for the record: my original name was |
forAll { (l: List[Int], o: Option[Int], m: Map[String, Int]) => | ||
assert(l.mapOrKeep { case i if i % 2 == 0 => i + 1 } === l.map(i => if (i % 2 == 0) i + 1 else i)) | ||
assert( | ||
o.mapOrKeep { case i if i > 0 => i + 1 } === (if (o.nonEmpty) if (o.get > 0) Some(o.get + 1) else o else None) | ||
) | ||
assert( | ||
m.mapOrKeep { case v if v % 2 == 0 => v + 1 } === m.map { case (k, v) => k -> (if (v % 2 == 0) v + 1 else v) } | ||
) | ||
} |
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.
Although it is a valid test case - no doubt in that, but it is not really necessary to invent a custom test logic there:
forAll { (l: List[Int], o: Option[Int], m: Map[String, Int], pf: PartialFunction[Int, Number]) =>
assertEquals(l.mapOrKeep(pf), l.map(x => pf.applyOrElse(x, _ => x: Integer))
// same for `o: Option[Int]` and `m: Map[String, Int]`
}
It would be way clearer and more generic.
Also:
PartialFunction[Int, Number]
so it check that theA1 >: A
constraint holds too.- I would probably go with
assertEquals
instead of justassert
because the former provides better reporting in a case of test failures.
That said, I feel that this equivalence could be just a part of the FunctorLaws
:
Functor[A].mapOrKeep(fa)(pf) <-> Functor[A].map(fa)(a => pf.applyOrElse(a, _ => a: A1)
If we could add such a rule to the laws, we would not need to check for special cases individually – all possible implementations would be got checked automatically.
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.
@satorg i guess I'm missing something again, but Int
is not a subtype of Number
type arguments [Int,Number] do not conform to method m's type parameter bounds [A,A1 >: A]
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.
Correct, Int
is definitely not, but Integer
is.
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.
But there are no generators for Integer. As I mentioned in another comment, I didn't find any examples of A and A1 with A1 >: A
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.
Well, I thought that since there's a generator for Int
then it wouldn't be too difficult to get one for Integer
via boxing.
But on the second thought, maybe it's not really worth the hassle – perhaps PartialFunction[Int, Int]
should be just enough for this sort of test.
Besides, if some method needs tests for syntax correctness (which may also include type correspondence/variance testing), then the SyntaxSuite
could be a better bet for that.
def testFunctor[F[_]: Functor, A, B]: Unit = { |
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.
given that we agreed to use PF[A, A] in the law test, seems like we should be covered by it, and it also uses syntax
Continuing bikeshedding on naming, there were several inquiries to consider the |
@danicheg |
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.
Looks great to me, thanks!
@johnynek could you please quickly re-review this? Thank you! |
Thank you for the PR |
* res0: List[Int] = List(1, 42, 3) | ||
* }}} | ||
*/ | ||
def mapOrKeep[A, A1 >: A](fa: F[A])(pf: PartialFunction[A, A1]): F[A1] = map(fa)(a => pf.applyOrElse(a, identity[A1])) |
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 thought we could use mapConserve
for List
, but it works only for AnyRef
.
Ideally collections would avoid allocations when we keep
.
I've found myself (and others) repeating this over and over, and it seems as primitive as
void
oras
, so would be nice to have it here. If this looks fine, i can also addflatMapOrKeep
toMonad
in another PR