I think you are fairly close to the simplest way of doing this. There are only a few tweaks I can think of.
First, I would really advice against using ``to`` as a variable name. It can be done, but it just looks ugly!
Second, you can reduce the number of cases to just three - if you first match the OK, Ok
and Error, Error
cases, you know that you now have one Error
and one Ok
, so you can join those using the |
(or) pattern and always return the Error
result:
let rangeToList (from, last) =
match from, last with
| Ok first, Ok last -> Ok [ first..last ]
| Error error1, Error error2 -> Error(error1 + "; " + error2)
| Error error, _ | _, Error error -> Error error
Third, if you are passing the two arguments to the function as a tuple, you could further condense this using the function
keyword which defines a function that immediately matches on the argument (but this would not work if the arguments were space separated):
let rangeToList = function
| Ok first, Ok last -> Ok [ first..last ]
| Error error1, Error error2 -> Error(error1 + "; " + error2)
| Error error, _ | _, Error error -> Error error
I think this solution works great. If you wanted something more clever using higher-order functions (but I really do not think this is needed here), you could define a zip
function that takes two results and combines them - producing a pair of values and collecting all errors into a list:
module Result =
let zip r1 r2 =
match r1, r2 with
| Ok v1, Ok v2 -> Ok (v1, v2)
| Error e1, Error e2 -> Error [e1; e2]
| Error e, _ | _, Error e -> Error [e]
Using this as a helper, you could now rewrite your original function as:
let rangeToList2 (from, last) =
Result.zip from last
|> Result.mapError (String.concat "; ")
|> Result.map (fun (first, last) -> [ first..last ])
I think this is nice, but perhaps unnecessarily clever. It is not much shorter than your first version and it is certainly not much easier to understand.