I have been porting some code written in terms of a concrete effect Either[String, A] to an abstracted effect with Raise/Handle, that is F[_]: Raise[*, String]
The experience reveals that Raise is not programmer-friendly in some frustrating ways.
I try to port this valueThru operator over HOCON Config objects, which:
- extracts a field from the config at a given path
- if path exists, attempts to parse it with a fallible function
extract
- if path doesnt exist, either fallback to a default if one is provided, or else fail
def valueThru[T](path: String, extract: (Config, String) => Either[String, T], optDefault: Option[T] = Option.empty): Either[String, T] =
if cfg.hasPath(path) then extract(cfg, path)
else optDefault.toRight(s"Not found: '$path' in ${cfg.origin().description()}")
The rewrite of this code to F[]: Raise is problematic:
def valueThru2[F[_]: RaiseS as R, T](path: String, extract: (Config, String) => F[T], optDefault: Option[T] = Option.empty): F[T] =
if cfg.hasPath(path) then extract(cfg, path)
else R.fromOption(optDefault)(s"Not found: '$path' in ${cfg.origin().description()}")
Hits compiler error:
No given instance of type cats.Applicative[F] was found for parameter F of method fromOption in trait Raise
where: F is a type in method valueThru2 with bounds <: [_] =>> Any
The problem is that the Raise typeclass offers fromOption but it can't get by without a little help from its Applicative friends. It only has a Functor internally and can't provide the needed pure to lift the option contents.
Also: I don't love the syntax. 4 terms where the original was 3, plus the need for an as R in the context bound (and even that needs recent Scala 3.x).
I feel like the API design hasn't started by asking "what does the programmer, the end-user, actually want?".
- They have an Option, they want the thing inside it.
- Raise an error if it's not there, want control over the error.
Well that reminds me of the age-old option.getOrElse(throw new Exception("My message")) from the standard library .
To me, the API I want is an extension method on Option[A], orRaise(e: E): F[A] that's available when either
- A Handle is in scope (Handle extends Raise and Applicative)
- A Raise and an Applicative is in scope
If offering both leads to implicit ambiguities, I'd take the 1. only. I'm doubtful how much use Raise will see without Handle in the wild.
I have been porting some code written in terms of a concrete effect
Either[String, A]to an abstracted effect with Raise/Handle, that isF[_]: Raise[*, String]The experience reveals that
Raiseis not programmer-friendly in some frustrating ways.I try to port this
valueThruoperator over HOCON Config objects, which:extractThe rewrite of this code to
F[]: Raiseis problematic:Hits compiler error:
The problem is that the
Raisetypeclass offersfromOptionbut it can't get by without a little help from its Applicative friends. It only has a Functor internally and can't provide the neededpureto lift the option contents.Also: I don't love the syntax. 4 terms where the original was 3, plus the need for an
as Rin the context bound (and even that needs recent Scala 3.x).I feel like the API design hasn't started by asking "what does the programmer, the end-user, actually want?".
Well that reminds me of the age-old
option.getOrElse(throw new Exception("My message"))from the standard library .To me, the API I want is an extension method on Option[A],
orRaise(e: E): F[A]that's available when eitherIf offering both leads to implicit ambiguities, I'd take the 1. only. I'm doubtful how much use Raise will see without Handle in the wild.