Как реализовать логику" return early " в F#

Я знаком с тем, что в 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 терпит неудачу с ошибкой, каждый другой шаг все еще вызывается.

есть "функциональный" способ "возвращения раньше" вместо вызова каждой функции в рабочем процессе, где мы должны проверять каждый раз на наличие ошибки?

4 ответов


вы пишете свои функции в предположении, что все прошло хорошо, как вы это сделали. Затем вы разворачиваете счастливый кейс и продолжаете счастливый кейс.

и в конце концов, вы можете использовать конструктор, чтобы сделать синтаксис довольно.

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 }

другие ответы велики, вычислительные выражения были бы идеальными для этого.

чтобы предоставить еще один вариант, стоит отметить, что в структуре кода 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 = ...
...

Это то, для чего предназначены выражения вычислений.

выражения вычисления обеспечивают хороший синтаксический сахар, чтобы сделать то, что известно как монадическая композиция, где предыдущее результирующее значение проверяется автоматически перед выполнением следующего шага.

недавно я говорил об этой концепции - она опубликована на youtube по адресу https://www.youtube.com/watch?v=gNNTuN6wWVc; и @scottwlaschin имеет подробное введение в него на http://fsharpforfunandprofit.com/series/computation-expressions.html

Ping меня на twitter, Если вы хотите больше помощи!


ответ Даниэля-синтаксический сахар для подхода к стилю продолжения. Вот версия без сахара:

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)))