如何在没有可变变量的情况下在F#中编写“重试”逻辑(只有1次迭代)?

时间:2016-07-19 10:13:26

标签: f# refactoring

因此,通过尝试避免可变变量,我提出了以下重试逻辑代码,这看起来很难看:

let result = TryConnect()
match result with
| ErrorConnecting ->
    SetupConnectionParameters()
    let resultAgain = TryConnect()
    match resultAgain with
    | ErrorConnecting ->
                      Console.Error.WriteLine("Setup failed!")
                      Environment.Exit(7)
    | Success(value) -> PerformOperations(value)
| Success(value) -> PerformOperations(value)

有没有办法减少一些重复? (请记住,没有mutable vars。) 谢谢!

6 个答案:

答案 0 :(得分:12)

由于这里显示了很多替代品,这里有另一个:

let private tryConnectNth n = 
    if n <> 0 then SetupConnectionParameters()
    TryConnect()

let isSuccess = function
    |Success _ -> true
    |ErrorConnecting -> false

let tryConnect n =
    Seq.init n tryConnectNth // generate a sequence of n connection attempts
    |> Seq.tryFind isSuccess // try to find the first success - returns Option
    |> Option.fold (fun _ -> id) ErrorConnecting // return ErrorConnecting if None, or result otherwise

仅在非零连接尝试时调用SetupConnectionParameters()并重复最多n次。

答案 1 :(得分:8)

使用重试参数递归函数:

let rec private tryToConnectAux tryAgain =
    match TryConnect() with
    | Success(value) -> PerformOperations(value)
    | ErrorConnecting when tryAgain ->
        SetupConnectionParameters ()
        tryToConnectAux false
    | ErrorConnecting ->
        Console.Error.WriteLine("Setup failed!")
        Environment.Exit(7)

通过tryToConnectAux true致电。

这个答案已被编辑。原始代码:

let rec tryConnecting nRetries =
    match TryConnect() with
    | ErrorConnecting ->
        if nRetries > 0 then tryConnecting (nRetries - 1)
        else
            Console.Error.WriteLine("Setup failed!")
            Environment.Exit(7)
    | Success(value) -> PerformOperations(value)

(此版本不包含SetupConnectionParameters(),您必须在适当的位置添加它)

答案 2 :(得分:8)

您可以将重试逻辑分离为单独的函数。这是一个有很多打印到控制台的例子来说明发生了什么。

let rec retry f tries =
    printfn "Trying..."
    match f () with
    | Some successValue ->
        printfn "Success"
        Some successValue
    | None ->
        match tries with
        | [] ->
            printfn "Failed"
            None
        | delayMs :: rest ->
            printfn "Waiting %i ms..." delayMs
            System.Threading.Thread.Sleep(delayMs:int)
            retry f rest

let random = System.Random()

let connect () =
    if random.Next(100) < 30 then Some "connection"
    else None

match retry connect [100; 100] with
| Some connection -> printfn "Do something with connection."
| None -> printfn "Could not connect."

尝试几次运行最后一个表达式。

  • 这为您提供了灵活的尝试次数,每次尝试都有可选的延迟(提供的延迟次数是重试次数)。

  • 应该可以调整代码以使用retry函数。您需要创建一个尝试连接一次的函数,如果成功则返回Some中包含的连接,如果失败则返回None。然后将该函数作为f参数传递。

答案 3 :(得分:2)

虽然我很欣赏@Vandroiy的尝试,但他的版块并不像我原来的代码那样(因为我故意不想第一次调用SetupConnectionParameters())。

这是我的结果,灵感来自他的回答和Jon的初步暗示:

let rec TryConnectAndMaybeSetup(retries) =
    if (retries > 1) then
        Console.Error.WriteLine("Setup failed")
        Environment.Exit(7)

    let result = TryConnect()
    match result with
    | ErrorConnecting ->
        SetupConnectionParameters()
        TryConnectAndMaybeSetup(retries + 1)
    | Success(value) -> PerformOperations(value)

TryConnectAndMaybeSetup(0)

这个替代方案也比@ TheQuickBrownFox更简单。

答案 4 :(得分:2)

这是另一种基于Vandroiy解决方案的解决方案,该解决方案仅在第一次失败时调用设置功能。<​​/ p>

let tryConnecting = 
    let rec connect nRetries setupFunction =
        match TryConnect() with
        | ErrorConnecting ->
            if nRetries > 0 then 
                setupFunction()
                connect (nRetries - 1) setupFunction
            else
                Console.Error.WriteLine("Setup failed!")
                Environment.Exit(7)
        | Success(value) -> PerformOperations(value)
    connect 1 SetupConnectionParameters

答案 5 :(得分:2)

这是一个基于Seq.unfold函数的迭代解决方案。我们使用此函数生成一系列成功/失败事件的延迟。然后,我们可以对此序列执行操作以获得成功结果,或者在重试之后停止。

首先让我们定义可能失败的函数的签名:

type ActionResult<'a> = 
    | Success of 'a
    | ErrorConnecting

type getValue<'a> = unit -> ActionResult<'a>

然后定义一个有区别的联合,它可以模拟我们在重试方面可能遇到的所有不同状态:

type Retry<'a> = 
    | Success of 'a * int
    | Failure of int
    | Untried

现在,给定上次重试的结果,我们生成序列中的下一个项目:

let unfolder (functionInvoke : getValue<_>) (retryParameters : Retry<_>) : ((Retry<_>* Retry<_>) option) = 

    let nextRetryResult () = 
        match functionInvoke() with
        | ActionResult.ErrorConnecting -> 
            match retryParameters with
            | Untried -> Failure 1
            | Failure pastRetries -> Failure (pastRetries + 1)
        | ActionResult.Success value -> 
            match retryParameters with
            | Untried -> Success (value, 0 )
            | Failure pastRetries -> Success (value, pastRetries )

    match retryParameters with
        | Untried 
        | Failure _ -> Some(retryParameters, nextRetryResult() )
        | success -> Some(retryParameters, success)

我们现在可以使用此函数创建getResultWithRetries函数:

let isNotSuccessAndLimitNotReached limit (retry : Retry<'a>) = 
    match retry with
    | Untried -> true
    | Failure retryCount when retryCount < limit -> true
    | _ -> false

let getResultWithRetries limit getValue   = 
    Seq.unfold (unfolder getValue) Retry.Untried 
    |> Seq.skipWhile(isNotSuccessAndLimitNotReached limit)
    |> Seq.head

我们终于可以测试一下:

let successValue = getResultWithRetries 3 (fun () -> ActionResult.Success "ABC")
let ``fail after 3 attempts`` : Retry<string> = getResultWithRetries 3 (fun () -> ActionResult.ErrorConnecting)
let ``fail after 5 attempts`` : Retry<string> = getResultWithRetries 5 (fun () -> ActionResult.ErrorConnecting)

使用以下函数,我们可以测试不纯函数会发生什么:

let succeedOn count = 
  let mutable callCount = 0
  let f () = 
    match callCount < count with
    | true -> 
      callCount <- callCount + 1
      ErrorConnecting
    | false -> ActionResult.Success "ABC"
  f


let ``result after 3 attempts when succeeds on 2nd`` : Retry<string> = getResultWithRetries 3 (succeedOn 2)
let ``result after 3 attempts when succeeds on 5th`` : Retry<string> = getResultWithRetries 3 (succeedOn 5)