Skip to content
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

Ease awkward constructions when doing something before raising #4635

Open
morgen-peschke opened this issue Jul 12, 2024 · 1 comment
Open

Comments

@morgen-peschke
Copy link
Contributor

A common pattern shows up in our code that doesn't seem to have an elegant solution: we'd like to log before raising an error (usually from an Option).

Currently, there are a couple ways to accomplish this for Option values (in rough order of increasing subjective elegance):

fooAlgebra.lookupOpt(id).flatMap {
  case Some(foo) => foo.pure[F]
  case None => log("Relevant context") >> CannotProcess.raiseError[F, Foo]
}

fooAlgebra.lookupOpt(id).flatMap {
  _.fold(log("Relevant context") >> CannotProcess.raiseError[F, Foo])(_.pure[F])
}

fooAlgebra.lookupOpt(id).flatMap {
  _.map(_.pure[F]).getOrElse(log("Relevant context") >> CannotProcess.raiseError[F, Foo])
}

fooAlgebra.lookupOpt(id)
  .flatTap(result => log("Relevant context").whenA(result.isEmpty))
  .flatMap(_.liftTo[F](CannotProcess))

The situation for Either (or Validated) is much better, but still not great:

fooAlgebra.lookupE(id).flatMap {
  case Right(foo) => foo.pure[F]
  case Left(FooNotFound) => log("Relevant context A") >> CannotProcess.raiseError[F, Foo]
  case Left(_) => log("Relevant context B") >> ServerError.raiseError[F, Foo]
}

fooAlgebra.lookupE(id).flatMap(_.fold(
  {
    case FooNotFound => log("Relevant context A") >> CannotProcess.raiseError[F, Foo]
    case _ => log("Relevant context B") >> ServerError.raiseError[F, Foo]
  },
  _.pure[F]
))

fooAlgebra.lookupE(id)
  .flatMap(_.leftTraverse {
    case FooNotFound => log("Relevant context A").as(CannotProcess).widen[LogicError]
    case _ => log("Relevant context B").as(ServerError).widen[LogicError]
  })
  .flatMap(_.liftTo[F])

I propose adding variants of ApplicativeError#from* to MonadError and their equivalent variants of the liftTo helpers in OptionSyntax EitherSyntax and ValidatedSyntax, which would allow writing the above as:

fooAlgebra.lookupOpt(id).flatMap(_.raiseNone {
  log("Relevant context").as(CannotProcess).widen[LogicError]
})

fooAlgebra.lookupE(id)
  .flatMap(_.leftMap[F[LogicError]] {
    case FooNotFound => log("Relevant context A").as(CannotProcess).widen
    case _ => log("Relevant context B").as(ServerError).widen
  })
  .flatMap(_.raiseLeft)
@satorg
Copy link
Contributor

satorg commented Jul 13, 2024

Perhaps, mouse can help out with that F[Option[...]] case:

import mouse.foption.*

def lookupSomething(id: ID): F[Option[Something]] = ???

val res: F[Something] =
  lookupSomething(id).getOrElseF {
    log(s"nothing found for $id") >>
      F.raiseError(new RuntimeException("not found"))
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants