F# Result型 便利関数
F#4.1で導入されたResult型ですが、導入しやすくするよう幾つか便利関数を定義してみました。
随時更新予定です。
Optionから変換
let ofOption (errorValue : 'error) (option : 'a option) : Result<'a, 'error> = if option.IsSome then Ok option.Value else Error errorValue
Sequence (Result list -> list Result)
Errorの値はlist化するようにしています。
let sequence (results : Result<'a, 'error> list) : Result<'a list, 'error list> = let folder x state = match x, state with | Ok h, Ok t -> Ok (h::t) | Ok _, Error t -> Error t | Error h, Ok _ -> Error [ h ] | Error h, Error t -> Error (h::t) List.foldBack folder results (Ok [])
ちなみにOptionだと下記のようになります。
let sequence (options : 'a option list) : 'a list option = List.foldBack (Option.map2 (fun x y -> x::y)) options (Some [])
mapResult
Result型にmapした後に上記のsequenceを適用すれば、listのmapが全て成功したかどうかが判定できます。
しかし途中でErrorとなった場合は以降のmapをする必要がない場合があります。
その時用にmapResultというものを定義してみました。
let mapResult (f : 'a -> Result<'b, 'error>) (list : 'a list) : Result<'b list, 'error> = List.fold (fun state item -> Result.bind (fun xs -> f item |> Result.map (fun x -> x::xs)) state) (Ok []) list |> Result.map List.rev
foldResult
foldの途中でErrorが存在すればErrorとなる、foldResultを作成してみました。
let foldResult (f : 'b -> 'a -> Result<'b, 'error>) (initialValue : 'b) (list : 'a list) : Result<'b, 'error> = List.fold (fun state x -> Result.bind (fun y -> f y x) state) (Ok initialValue) list
ResultBuilder
Resultをコンピュテーション式に対応させてみます。
Option型と全く同じ内容となっています。
type ResultBuilder() = member this.Bind(x, f) = Result.bind f x member this.Return(x) = Ok x member this.ReturnFrom(x) = x let result = ResultBuilder()
下記のように利用できます。
let divide x y = if y = 0.0 then Error "zero div" else Ok (x / y) let x = result { let! a = divide 100.0 2.0 let! b = divide a 2.0 let! c = divide b 0.0 let! d = divide c 2.0 return d } // Error "zero div"