reazen / relude

FP-inspired prelude/standard library for ReasonML projects
https://reazen.github.io/relude
MIT License
268 stars 41 forks source link

Suggestion: Add extraction helpers for Option / Result? #285

Closed rolandpeelen closed 3 years ago

rolandpeelen commented 3 years ago

Hi Guys,

I've recently started using Relude over the Belt lib. Some functions that are still leftover from our own helper library are the ones below. Basically, for either an option, or a result, for 'extractWith', do sort of a bimap, but don't wrap it back up into the functor (this is really helpful in for instance server responses, where you'd want to extract the result to either a 200 http response, or some error, but the error is heavily context dependent). Same for biExtractTo, but then you don't really care about the value, just wether or not the computation succeeded. This is helpful in some parser situations, where you don't care about the parsed value, but just want to return some other stuff.

Basically, these save a lot of pattern matching.

I couldn't find these in the library, possibly for good reason, but would you guys be open to adding them? I'd be happy to create the PR :)

// Option
let biExtractWith = (left, right, data) =>
  switch (data) {
  | Some(data) => left(data)
  | None => right()
  };
let biExtractTo = (left, right, data) =>
  switch (data) {
  | Some(_) => right
  | None => left 
  };
  // Result
let biExtractWith =
    (left: 'a => 'c, right: 'b => 'd, result: Belt.Result.t('a, 'b)) =>
  switch (result) {
  | Ok(a) => left(a)
  | Error(b) => right(b)
  };

  let biExtractTo = (left, right, data) =>
  switch (data) {
  | Ok(_) => right
  | Error(_) => left 
  };

let extractLeftWith = (left, result) =>
  biExtractWith(left, Lib_Generic.identity, result);
let extractRightWith = (right, result) =>
  biExtractWith(Lib_Generic.identity, right, result);
mlms13 commented 3 years ago

Hi, thanks for the suggestion! Have you seen Option.fold, Option.foldLazy, and Result.fold? My gut says those functions are very close to what you're looking for, but I do have a couple questions about your functions.

For Result.biExtractWith:

let biExtractWith =
  (left: 'a => 'c, right: 'b => 'd, result: Belt.Result.t('a, 'b)) =>
  switch (result) {
  | Ok(a) => left(a)
  | Error(b) => right(b)
  };

What is the return type of the function? If left returns a 'c, then right should also return a 'c (instead of a 'd), correct? Which would mean that the whole function returns a 'c (no longer wrapped in a result). If I understand that correctly, that should be covered by Result.fold.

I don't think we have anything quite like extractLeftWith and extractRightWith. Our Result.handleError lets you convert error values to ok values, but the returned type is still a result (although the error channel is void, so you can safely get out of the result). Our Result.getOrElse might be even closer, but it doesn't provide you with the error value for constructing the fallback 'a. This is different from the version in fp-ts, and I wonder if we should consider changing our definition (although that would be a breaking change).

I'm not quite as convinced about extractLeftWith (converting an ok result into an error value), since that seems like a less common need, and you could always Result.flip >> Result.getOrElse, if our getOrElse worked as described above.

rolandpeelen commented 3 years ago

Hi!

Thanks for the thorough response. It actually got me thinking and you are 100% right. Both do map to a type 'c and as such, they are covered by fold. That's great. My use-case - http responses - are actually response(some-ok-json) or response(some-err-json) - ie. both the same type.

With regards to the extract left / right. They are pretty much derivatives of fold where one would throw out the other side. The nice thing is there is no mangling it through an option with the help of getOk. getOrElse would work, perhaps a variant getOrElseWith (or getOrElseBy) could be introduced to make it a non-breaking change? I think that 95% of the use-cases would be getOrElse and adding the value to everything might be a bit much.

I agree that extractLeftWith is less used (I might have just wrote it for completeness, not sure we even use it somewhere).

My main go-to in a lot of cases is the fold, so I'm happy that's there. If you guys are keen on adding a way to get the error value out, I think that would be great! I'd be happy to create a PR for that, direct me in the way you'd like (breaking or non-breaking) and I'll make the code :)

mlms13 commented 3 years ago

I think that makes a lot of sense! We've generally avoided left/right in names, since the OCaml world has focused on result (rather than either) with its ok/error constructors, so maybe it would make sense to add the following:

Would that cover your needs? If so, I'd say feel free to add them!

rolandpeelen commented 3 years ago

Awesome, I think those make sense. I'll try to find some time and make a PR 👍