在 F# 中获取第一个有效结果的计算表达式

A computation expression to get the first valid result out, in F#

我怎样才能以干净的方式实现这样的目标?

让我们想象一下这个简单的代码:

let a () = checkSomeStuff ();     None
let b () = do Something ();       Some "thing"
let c () = checkSomethingElse (); None


"getOne" {
    do! a()
    do! b()
    do! c()
}

它会 return 第一个“Some”。

我可以通过使用 Result 来实现这个确切的行为,我会 return 通过一个 Error 的值并继续通过 Ok,但这不可读/很好:

let a () = checkSomeStuff ();     Ok ()
let b () = do Something ();       Error "thing"
let c () = checkSomethingElse (); Ok ()


result {
    do! a()
    do! b()
    do! c()
}

这可行,但我希望在不误用 Result 类型的情况下实现这一点。可以用现有的表达式来完成吗?

前段时间,我写了一篇关于 imperative computation expression builder 的 post 文章,它按照这些思路做了一些事情。您可以将计算表示为 option-returning 函数:

type Imperative<'T> = unit -> option<'T>

在计算生成器中,最主要的是 Combine 表示操作顺序的操作,但您还需要一些其他操作才能使其工作:

type ImperativeBuilder() = 
  member x.ReturnFrom(v) = v
  member x.Return(v) = (fun () -> Some(v))
  member x.Zero() = (fun () -> None)
  member x.Delay(f:unit -> Imperative<_>) = 
    (fun () -> f()())
  member x.Combine(a, b) = (fun () ->
    match a() with 
    | Some(v) -> Some(v) 
    | _ -> b() )

let imperative = new ImperativeBuilder()  

然后您可以重新实现您的示例 - return 一个值,您只需使用 return,但您需要使用 return! 组合各个操作,因为构建器不支持do!:

let a () = imperative { printfn "one" }
let b () : Imperative<string> = imperative { return "result" }
let c () = imperative { printfn "two" }

let f = imperative {
    return! a()
    return! b()
    return! c()
}

f()

您不需要计算表达式。 F# 有一个名为 Seq.tryPick 的 built-in 函数,它将给定函数应用于序列的连续元素,返回第一个 Some 结果(如果有)。您可以使用 tryPick 来定义 getOne,如下所示:

let getOne fs =
    fs |> Seq.tryPick (fun f -> f ())

用你的例子试试:

let a () = checkSomeStuff ();
let b () = Something ();
let c () = checkSomethingElse ();

let x = getOne [ a; b; c ]
printfn "%A" x   // Some "thing"

您可以创建一个函数来执行您想要的操作。但是你必须考虑清楚你想做什么。

所以,你的逻辑是。

  1. 你执行了一个 return 是 option
  2. 的函数
  3. 然后你检查option。如果它是 None 你执行另一个函数,如果它是 Some 你 return 值。

像这样的函数可能如下所示:

let getSome f opt =
    match opt with
    | None   -> f ()
    | Some x -> Some x

有了这样的功能,你就可以写了。 ***

let x =
    checkSomeStuff ()
    |> getSome (fun _ -> Something () )
    |> getSome checkSomethingElse

但后来我想,嗯……getSome 没有更好的名字吗?在某种程度上我想说:

Execute some code and check if it is Some, or else pick the next thing.

考虑到这一点,我想。嗯....不是已经有Option.orElse了吗?是的!有!还有一个 Option.orElseWith 功能,可以更好地满足您的需求。所以现在,你可以写了。

let y =
    checkSomeStuff ()
    |> Option.orElseWith (fun _ -> Something () )
    |> Option.orElseWith checkSomethingElse

如果你有side-effects的功能,那么你应该使用Option.orElseWith,否则,你可以起诉Option.orElse


***:我假设您定义了以下函数

let checkSomeStuff () =
    None

let Something () =
    Some "thing"

let checkSomethingElse () =
    None