Как закодировать логику "повторить" (только с 1 итерацией) в F # без изменчивых варов?
Итак, пытаясь избежать изменчивых переменных, я придумал следующий код повторной логики, который кажется уродливым:
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.)
Спасибо!
Ответы
Ответ 1
Поскольку здесь представлено много альтернатив, вот еще один:
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 раз.
Ответ 2
Сделайте функцию рекурсивной с параметром для повторений:
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()
, вы должны добавить ее в любое место)
Ответ 3
Вы можете разделить логику повтора на отдельную функцию. Вот пример с большим количеством печати на консоль, чтобы проиллюстрировать, что происходит.
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
.
Ответ 4
В то время как я ценю, что @Vandroiy пытается, его блок не точно ведет себя как мой исходный код (потому что я намеренно не хочу называть SetupConnectionParameters()
в первый раз).
Это мой результат, вдохновленный его ответом и первоначальным намеком Джона:
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's.
Ответ 5
Здесь другое решение, основанное на решении Vandroiy, которое вызывает только функцию настройки при первом сбое.
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
Ответ 6
Вот итерационное решение, основанное на функции Seq.unfold
. Мы используем эту функцию для генерации ленивой последовательности событий Success/Failed. Затем мы можем выполнить манипуляции в этой последовательности, чтобы получить успешный результат, или остановимся после нескольких попыток.
Сначала определим сигнатуру функции, которая может выйти из строя:
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)