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"