Skip to content

Ergonomics of Raise need improvement #645

@benhutchison

Description

@benhutchison

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

  1. A Handle is in scope (Handle extends Raise and Applicative)
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions