Объедините асинхронные и дополнительные монады

При написании некоторого кода, который в последнее время работает с множеством вложенных асинхронных рабочих процессов, я обнаружил появление паттерна, который пахнет для меня. Простой пример:

let flip f x y = f y x
let slowInc x = async {
    do! Async.Sleep 500
    printfn "Here you go, %d" x
}

let verboseFun inp = async {
    match List.tryFind (flip (>) 3) inp with
    | Some x -> do! slowInc x
    | _ -> ()
}

verboseFun [1..5] |> Async.RunSynchronously

"VerboseFun" для меня кажется многословным, но я не могу придумать, как объединить монады Option и Async, чтобы их можно было переписать без сопоставления с образцом. Я думал что-то вроде

let terseFun inp = async {
    inp
    |> List.tryFind (flip (>) 3)
    |> Option.iterAsync slowInc
}

Мне просто показалось, что, скорее всего, я просто не знаю, какие строительные блоки доступны для этого.

РЕДАКТИРОВАТЬ: дополнительные разъяснения после ответа Томаса.

Я пытался приспособить то, что было бы для меня тривиально, если бы все было синхронно, например,

let terseFun inp =
    inp
    |> List.tryFind (flip (>) 3)
    |> Option.iter someSideEffectFunciton

стать частью вложенных асинхронных рабочих процессов. Первоначально я думал "просто брось делать!", Поэтому придумал

let terseFun inp = async {
    inp
    |> List.tryFind (flip (>) 3)
    |> Option.iter (fun x -> async { do! someSideEffectFunciton x })
    |> ignore
}

Но это немедленно пахло неправильно для меня, потому что VS начал требовать игнорирования. Надеюсь, это поможет уточнить.

1 ответ

Решение

Библиотека ExtCore имеет множество вспомогательных функций, которые позволяют работать с асинхронными вычислениями, которые возвращают необязательные значения, т.е. типа Async<'T option> и это даже определяет asyncMaybe построитель вычислений для работы с ними.

Я не использовал его широко, но из нескольких простых экспериментов, которые я сделал, похоже, что он не так хорошо интегрирован с остальными F# async функциональность, как это может быть, но если вы хотите пойти в этом направлении, ExtCore, вероятно, лучшая библиотека.

Следующее использует iter функция от AsyncMaybe.Array ( источник здесь). Это немного некрасиво, потому что я должен был сделать slowInc быть типа Async<unit option>, но это довольно близко к тому, что вы просили:

let slowInc x = async {
    do! Async.Sleep 500
    printfn "Here you go, %d" x
    return Some ()
}

let verboseFun inp = 
  inp 
  |> List.tryFind (fun x -> 3 > x) 
  |> Array.ofSeq
  |> AsyncMaybe.Array.iter slowInc 
  |> Async.Ignore

Кроме того, я также удалил ваш flip функция, потому что это обычно не рекомендуемый стиль в F# (это делает код загадочным).

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

let asyncIter f inp = async {
  match inp with 
  | None -> ()
  | Some v -> do! f v }

let verboseFun inp = 
   inp 
   |> List.tryFind (fun x -> x > 3) 
   |> asyncIter slowInc

Самое замечательное в F# заключается в том, что эти абстракции действительно легко написать самостоятельно и сделать так, чтобы они точно соответствовали вашим потребностям:-)

Другие вопросы по тегам