I have put together this f# code in an attempt to solve the following problem
- Given a mapping operation that may not be able to return a value
- Cease mapping over the remaining items of a sequence if the mapping operation fails
- Append the error to items already mapped and return early
This cannot be achieved with the inbuilt Seq.map
and I could not locate any other existing implementations
type OptionFunction<'a, 'b> = 'a -> 'b option
type ResultFunction<'a, 'b, 'c> = 'a -> Result<'b, 'c>
type MaybeProjection<'a,'b,'c> =
| OptionProjection of OptionFunction<'a,'b>
| ResultProjection of ResultFunction<'a,'b,'c>
type OptionResultBuilder<'e>() =
member _.Return(x) = Result.Ok x
member _.Bind(m,f) =
let r = match m with
| Some a -> Ok a
| None -> Error Unchecked.defaultof<'e>
Result.bind f r
member _.Bind(m,f) = Result.bind f m
let orb = OptionResultBuilder()
let mapTillFail mapper s =
let rec inner sq =
seq {
let mapped = orb {
let! head = Seq.tryHead sq
match mapper with
| OptionProjection a ->
let! m = a head
return m
| ResultProjection a ->
let! m = a head
return m
}
match mapped with
| Error _ as err-> yield err
| Ok _ ->
yield mapped
yield! sq |> Seq.skip 1 |> inner
}
inner s
//code to test it, not part of the implementation
let r = Seq.empty
let s = seq{1..10}
let op = OptionProjection (fun x -> if x < 5 then Some x else None)
let rp = ResultProjection (fun x -> if x < 5 then Ok x else Error "if >= 5")
r|> mapTillFail op
s|> mapTillFail op
r|> mapTillFail rp
s|> mapTillFail rp
The inbuilt Option
and Result
types are a natural fit, and I wanted to be able to use a mapping function that returned either of these types.
Despite several attempts to refactor the duplicate statements inside the match mapper with
, I just could not not work out how to satisfy the compiler. Every attempt results in the compiler complaining that it can't find a suitable overload for Bind. Suggestions ?
Beyond that I'm largely happy with it for my purposes. But I welcome any other improvements, or comments on issues like performance (at this stage I've not stress tested it).