-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add unit[F] syntax to cats.syntax.applicative
#4786
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
base: main
Are you sure you want to change the base?
Conversation
|
Thank you for the PR! But to be honest, I'm not sure it would be a good idea to start bringing import cats.syntax.all.*
def foo[A](o: Option[A]) = ...
foo(none) // very clear
foo(unit) // ???The important difference between Besides, there are ways already to achieve pretty much the same conciseness: def bar[F[_]](implicit F: Applicative[F]) = {
...
F.unit // arguably even better than `unit[F]`
}Here Moreover, on Scala3 it can look even better: def bar[F[_]: Applicative as F] =
...
F.unit |
|
as for the Yet people at my job still complain about using |
|
it might be better to |
|
Also, for what it's worth, I'd like to note that method one(a).two(b, c).three(d, e, f) *> F.unit
// VS
one(a).two(b, c).three(d, e, f).voidif (condition) sideFffectWithoutValue else F.unit
// VS
sideFffectWithoutValue.whenA(condition)Those don't cover all possible cases, of course, but quite helpful in many of them.
It shouldn't be a big issue anyway: class Foo[F[_]: Applicative](...) {
private val F = Applicative[F] // <-- just add this when necessary
def bar(...) = {
...
F.unit
}
}Moreover, taking into account this experimental (for now) yet pretty expressive feature: it would be no exaggeration to say that matching context bound names with their types is becoming a de facto standard. |
Cats usually uses upper case letters appended to function names: That said, I personally not a big fan of one letter "modifiers" since they look cryptic. I don't believe short names should be the ultimate goal, especially if it sacrifices clarity.
|
Actually, we use it a bit differently:
Here's a very simplified example for both cases: trait SendingService[F[_]] {
def send(something: Something): F[Unit]
}
def implementation[F[_]: Monad](actor: Actor[F], log: Log[F]) = new SendingService[F[_]] {
def send(something: Something): F[Unit] =
actor.ask(something).flatMap {
case State(_) => ().pure[F]
case Response(r) => log.info(r)
case Error(err) => new Error(err).raiseError[F, Unit]
}
}
def empty[F[_]: Applicative] = new SendingService[F[_]] {
def send(something: Something): F[Unit] = ().pure[F]
} We have traits with dozens of methods that we add
it still requires some additional effort which is just not worth it in the end. Copy-pasting |
I am very surprised it's not been proposed before, but searching for something similar in pull request resulted in nothing.
The motivation is that
().pure[F]is used quite often and it's a lot more typing than a simpleunit[F](three symbols more! that's a lot when there are hundreds of them in a project.)There's
IO.unit,Resource.unit, it's even similar tonone[A], so this one tries to replicate it, but without a companion object to call thisdeffrom.