Объединить F # async и, возможно, выражение вычисления

Скажем, я хочу вернуть Option в рабочий процесс async:

let run = 
    async {
        let! x = doAsyncThing
        let! y = doNextAsyncThing x
        match y with
        | None -> return None
        | Some z -> return Some <| f z
    }

В идеале я бы использовал, возможно, выражение вычисления из FSharpx одновременно с async, чтобы избежать выполнения match. Я мог бы создать пользовательский построитель, но есть ли способ объединить два выражения вычислений? Это может выглядеть примерно так:

let run = 
    async {
        let! x = doAsyncThing
        let! y = doNextAsyncThing x
        return! f y
    }

Ответ 1

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

В качестве альтернативы вы можете использовать F # +, который является проектом, который предоставляет общие рабочие процессы для монад, и в этом случае он будет автоматически получен для вы, здесь рабочий пример, используя ваш рабочий процесс, а затем используя OptionT, который является монадным трансформатором:

#nowarn "3186"
#r @"FSharpPlus.dll"

open FSharpPlus
open FSharpPlus.Data

let doAsyncThing = async {return System.DateTime.Now}
let doNextAsyncThing (x:System.DateTime) = async {
    let m = x.Millisecond  
    return (if m < 500 then Some m else None)}
let f x = 2 * x

// then you can use Async<_> (same as your code)
let run = monad {
    let! x = doAsyncThing
    let! y = doNextAsyncThing x
    match y with
    | None   -> return None
    | Some z -> return Some <| f z}

let res = Async.RunSynchronously run

// or you can use OptionT<Async<_>> (monad transformer)

let run' = monad {
    let! x = lift doAsyncThing
    let! y = OptionT (doNextAsyncThing x)
    return f y}

let res' = run' |> OptionT.run |> Async.RunSynchronously

Первая функция должна быть "поднята" в другую монаду, потому что она имеет дело только с async (а не с Option), вторая функция имеет дело с обоими, поэтому ее нужно только "упаковать" в нашу OptionT DU.

Как вы можете видеть, оба рабочих процесса производятся автоматически, тот, который у вас был (рабочий процесс async) и тот, который вы хотите.

Для получения дополнительной информации об этом подходе прочитайте Monad Transformers.

Ответ 2

Простым способом сделать это является использование Дополнительный модуль:

let run = 
    async {
        let! x = doAsyncThing
        let! y = doNextAsyncThing x
        return Option.map f y
    }

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

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

Ответ 3

type MaybeMonad() = 
    member __.Bind(x, f) = 
        match x with
        | Some v -> f v
        | None -> None
    member __.Return(x) = 
        Some x

let maybe = MaybeMonad()

let run = async {
    let! x = doAsyncThing
    let! y = doNextAsyncThing x
    return maybe {
        let! y_val = y
        return f y_val
    }
}

просто используйте f # Выражения вычисления внутри.