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 : RBadTree
A 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 : RBadTree
rreturn
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 tbad
When 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.Empty
However, 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.