【问题标题】:How to implement "return early" logic in F#如何在 F# 中实现“提前返回”逻辑
【发布时间】:2015-04-24 14:43:09
【问题描述】:

我知道在 F# 中没有等效的“return”关键字这一事实。

但是,我们最近遇到了一个问题,我们需要一个包含许多步骤的工作流程,其中每个步骤都可以返回好的或坏的结果。如果在任何步骤中发现错误结果,我们希望退出工作流程 - 并提前退出!

我们通过在每个步骤(即函数)中有效地检查错误来解决它,但我认为这不是正确的方法 - 它效率低下,我们不会提前退出。

工作流中的一个示例函数如下:

let StepB stepAResult someParameters =
    match stepAResult with
    | Good(validResourceData) ->
        // Do current step processing
        // Return current step result
    | Error(error) -> error |> Result.Error

工作流程本身如下:

let Workflow someParameters =
    let stepAResult = StepA someParameters
    let stepBResult = StepB stepAResult someParameters
    let stepCResult = StepC stepBResult someParameters
    let stepDResult = StepD stepCResult someParameters
    stepDResult

所以每个示例函数都会接受前一个函数的结果,并且只有在没有错误的情况下才执行当前步骤!

我遇到的问题是,如果 StepA 因错误而失败,则仍会调用所有其他步骤。

是否有一种“功能性”的方式“提前返回”,而不是调用工作流中的每个函数,我们必须每次都检查错误?

【问题讨论】:

标签: f# functional-programming purely-functional


【解决方案1】:

您在假设一切顺利的情况下编写函数。然后你打开快乐的盒子,继续快乐的盒子。

最后,您可以使用构建器使语法更漂亮。

type Result<'TSuccess, 'TError> = 
    | Success of 'TSuccess
    | Error of 'TError

type ResultBuilder() =
    member this.Bind(v, f) =
        match v with
        | Success v -> f v
        | Error e -> Error e

    member this.Return value = Success value

let result = ResultBuilder()

let bla<'a> = result {
    let! successOne = Success 1
    let! successTwo = Success 2
    let! failure = Error "after this, the computation exited"
    failwith "Boom, won't occurr"
    return successOne + successTwo }

【讨论】:

  • 这个建议或多或少是我们所采用的,但它确实意味着您不会“提前返回”。工作流中的每个功能都必须有效地“检查”错误情况。感谢您澄清这一点!
  • 这里要记住的一点是,所有阶段都必须执行,因为每个阶段都可以改变成功或错误类型,并且最后必须有确定性的结果类型。
【解决方案2】:

其他答案很好,计算表达式将是完美的。

只是为了提供另一种选择,值得注意的是,F# 代码结构中存在一些特殊情况,可以让“提前返回”的故事变得不那么痛苦。

规范的基于缩进的格式可能是这样的混乱:

let step1 = ...
if failed step1 then
    () // bail out
else
    let step2 = ...
    if failed step2 then
        ()
    else
        let step3 = ...
        if failed step3 then
            ()
        else 
            let step4 = ...
            ...

下面有两种替代格式。它们看起来很奇怪,但实际上非常方便。

let step1 = ...
if failed step1 then
    () // bail out
else

let step2 = ...
if failed step2 then
    ()
else

let step3 = ...
if failed step3 then
    ()
else 

let step4 = ...
...

let step1 = ...
if failed step1 then () else

let step2 = ...
if failed step2 then () else

let step3 = ...
if failed step3 then () else 

let step4 = ...
...

【讨论】:

    【解决方案3】:

    这就是计算表达式的用途。

    计算表达式提供了很好的语法糖来执行所谓的一元组合,在执行下一步之前自动检查先前的结果值。

    我最近就这个概念做了一次演讲——它发布在 youtube 上的https://www.youtube.com/watch?v=gNNTuN6wWVc;并且@scottwlaschin 有详细的介绍在http://fsharpforfunandprofit.com/series/computation-expressions.html

    如果您需要更多帮助,请在推特上联系我!

    【讨论】:

    • 但这是否意味着我们必须每一步都检查?如果步骤 A 失败并出现错误,我想退出,我不想在步骤 B、C 和 D 中检查这个?
    • “退出”是什么意思? * 如果这意味着您不执行工作流中的任何后续功能,这正是工作流中发生的情况。 * 如果这意味着您不对结果值进行任何进一步检查,那么实际上无法以一般方式进行。“退出”的概念是一种非常必要的思维方式的遗留物——你会通过使用由安全地链接多个函数组成的值的概念来更好地服务......
    • 另外,您不必自己编写检查代码 - 您只需在“成功”情况下提供参与工作流的函数,功能机制会提升这些函数以应用检查代码为您...所以您不必担心在命令式代码中遇到的代码膨胀或维护问题!
    【解决方案4】:

    Daniel 的答案是语法糖到延续风格的方法。 这里是脱糖版本:

    let step1 parm cont =
        if true then cont 42 else None
    let step2 parm cont =
        if false then cont 69 else None
    let conti parm =
        step1 parm (fun result1 -> 
        step2 parm (fun result2 -> 
        Some(result1 + result2)))
    

    【讨论】:

      猜你喜欢
      • 2019-09-19
      • 1970-01-01
      • 2020-05-09
      • 2012-12-19
      • 2013-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多