Part 1: https://gist.github.com/mrange/1d2f3a26ca039588726fd3bd43cc8df3
Full source: https://gist.github.com/mrange/67553b312bd6a952690defe4bce3b126
In part 1 I briefly described RResult<_> that I find sometimes is better suited for ROP than Option<_> (because RResult<_> allows capture of error information) or Result<_,_> (because RResult<_> has a homogeneous error type):
[<RequireQualifiedAccess>]
[<Struct>]
type RResult<'T> =
| Good of good : 'T
| Bad of bad : RBadTreeA problem with RResult<_> compared to Option<_> is that there is no nice way to represent an empty value like None. However, it is rather easy to expand RResult<_> to support empty values:
[<RequireQualifiedAccess>]
[<Struct>]
type RResult<'T> =
| Good of good : 'T
| Empty
| Bad of bad : RBadTreerreturn and rbind are easy to implement:
let inline rreturn v = RResult.Good v
let inline rbind (uf : 'T -> RResult<'U>) (t : RResult<'T>) : RResult<'U> =
match t with
| RResult.Good tgood -> uf tgood
| RResult.Empty -> RResult.Empty
| RResult.Bad tbad -> RResult.Bad tbadWhen we like to combine two RResult<_> using for example rpair the question arise how we
should combine Empty and Bad result. For rpair I believe it makes sense to return the Bad result as this is the most serious problem that caused rpair to fail.
let rpair (u : RResult<'U>) (t : RResult<'T>) : RResult<'T*'U> =
// Note that: Empty && Bad ==> Bad
match t, u with
| RResult.Good tgood , RResult.Good ugood -> rreturn (tgood, ugood)
| RResult.Bad tbad , RResult.Bad ubad -> RResult.Bad (tbad.Join ubad)
| _ , RResult.Bad ubad -> RResult.Bad ubad
| RResult.Bad tbad , _ -> RResult.Bad tbad
| _ , _ -> RResult.EmptyHowever, for rorElse it I think it makes sense to return the "least faulty value" as that is the purpose of rorElse. That means if we combine an Empty and Bad result we should return Empty.
let rorElse (s : RResult<'T>) (f : RResult<'T>) : RResult<'T> =
// Note that: Empty || Bad ==> Empty
match f, s with
| RResult.Good _ , _ -> f
| _ , RResult.Good _ -> s
| RResult.Empty , _ -> RResult.Empty
| _ , RResult.Empty -> RResult.Empty
| RResult.Bad fbad, RResult.Bad sbad -> RResult.Bad (fbad.Join sbad)The API for RResult<_> is mostly unchanged except that pattern matching on RResult<_> must include an Empty case.
My initial attempt at work with RResult<_> was basically Result<_,_> with a fixed error type. Over time the need to represent an empty good value grew and I found it useful to extend RResult<_> with an Empty value.
This has allowed me to combine functions that has a tristate outcome using ROP in a clean and a succinct manner.
I hope this was interesting.