-
-
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 semiflatTap and leftSemiflatTap functions to EitherT #3316
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3316 +/- ##
==========================================
+ Coverage 93.2% 93.42% +0.22%
==========================================
Files 377 378 +1
Lines 7604 7631 +27
Branches 199 206 +7
==========================================
+ Hits 7087 7129 +42
+ Misses 517 502 -15
Continue to review full report at Codecov.
|
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 like this addition, thanks!
This is a case where I'd personally be inclined to move the implementation down a couple of levels:
def semiflatTap[C](f: B => F[C])(implicit F: Monad[F]): EitherT[F, A, B] =
EitherT(F.flatMap(value) {
case l @ Left(_) => F.pure(EitherUtil.rightCast(l))
case Right(b) => F.as(f(b), Right(b))
})
This isn't quite as clear as your version, but in my view the difference in readability is pretty small, and it avoids a lot of unnecessary wrapping and unwrapping. I don't think we need to worry too much about optimization here, but if we can avoid some allocations more or less for free I think we should.
(For what it's worth I also think we should do the same thing for the existing semiflatMap
and leftSemiflatMap
implementations.)
@travisbrown I'm not fully convinced ;) In the end it uses So it adds one wrap/unwrap for a price of more readable code and additionally existing code is reused. Plus it isn't a method like However I'm not that attached to the original solution, so can change it if you insist ;) |
@matwojcik It's not a blocker for me. The signature is the important part, if someone wants to make an argument that we should change the implementations later they can. |
The test failure was I have an only moderately controversial fix for that problem that's waiting for another sign-off: #3303 |
def semiflatTap[C](f: B => F[C])(implicit F: Monad[F]): EitherT[F, A, B] = | ||
semiflatMap(b => F.as(f(b), b)) | ||
|
||
def leftSemiflatTap[C](f: A => F[C])(implicit F: Monad[F]): EitherT[F, A, 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.
What do we think about using Unit
here to signify that this resulting type is going to be discarded? Might be a bit more cumbersome on the usage side, but it's just an extra .void
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.
We already have a flatTap
on FlatMap
that doesn't require Unit
, and tap
, tapWith
, etc. on Kleisli
don't constrain the output type to be Unit
. My personal preference would be consistency—I think that plus slightly easier use outweighs a little extra expressivity in the types.
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.
@LukaJCB I first thought about it, but then saw that FlatMap[F].flatTap
takes A => F[B]
instead of A => F[Unit]
, so I wanted to be consistent with existing signature.
semiflatTaps
functions onEitherT
are highly useful when you have some e.g.Logger[F]
abstractions and you don't want to lift it toEitherT[F,A,B]