简体   繁体   English

F#自定义计算工作流程

[英]F# custom computation workflow

Given the result type 给出结果类型

type Result<'t> = OK of 't | Error of string 

I have these functions which all return Async < Result<'t> > which are combined something like this: 我有这些函数都返回Async <Result <'t>>这些函数组合如下:

let a = async { return Result.OK 1000 }
let b = async { return Result.Error "some message" }

let sum x y = 
    async {
        let! r1 = x
        match r1 with
        | Result.OK v1 -> 
             let! r2 = y
             match r2 with
             | Result.OK v2 -> return v1 + v2
             | Result.Error msg -> return Result.Error msg
        | Result.Error msg -> return Result.Error msg
    }

This code looks bad so instead I would like to have this: 这段代码看起来很糟糕,所以我希望这样:

type Result = Ok of int | Error of string

type MyMonadBuilder() =
    member x.Bind (v,f) = 
        async { 
            let! r = v
            match r with
            | Ok r' -> return! f r'
            | Error msg -> return Error msg
        }

    member x.Return v = async {return Ok v }

    member x.Delay(f) = f()

let mymonad = MyMonadBuilder()
let runMyMonad = Async.RunSynchronously

let a = mymonad { return 10 }
let b = mymonad { return 20 }

let c = 
    mymonad { 
        return Result.Error "Some message"
        //??? The above doesn't work but how do I return a failure here?
    }

let d = 
    async {
        return Ok 1000
    } 
    //how to wrap this async with mymonad such that I can use it together with my other computation expressions?

let sum x y = 
    mymonad {
        let! v1 = x
        let! v2 = y
        return v1 + v2
    }

[<EntryPoint>]
let main argv = 
    let v = sum a b |> runMyMonad
    match v with
    | Ok v' -> printfn "Ok: %A" v'
    | Error msg -> printf "Error: %s" msg

    System.Console.Read() |> ignore
    0 

So the questions are: 所以问题是:

  1. How do I write function c such that it returns an error in mymonad? 如何编写函数c使其在mymonad中返回错误?
  2. How do I write function d such that it wraps an async with a mymonad? 如何编写函数d,使其包含与mymonad的异步?
  3. How can I make my monad parameterized in a similar way Async is? 如何以与Async类似的方式使我的monad参数化?

...such that I can write ......这样我就可以写了

let f (a:MyMonad<int>) (b:MyMonad<string>) = ...

UPDATE: 更新:

Also I would like to run several mymonad operations in parallel and then look at the array of results to see what were the errors and the successes. 另外,我想并行运行几个mymonad操作,然后查看结果数组以查看错误和成功之处。 For this reason I think using exceptions is not a good idea. 出于这个原因,我认为使用例外不是一个好主意。

Also, regarding the question 3, what I meant was to have my type parameterized and opaque such that the callers don't know/don't care they are dealing with an async. 另外,关于问题3,我的意思是使我的类型参数化并且不透明,以使调用者不知道/不在乎他们正在处理异步。 The way I wrote the monad the caller can always use Async.RunSynchronously to run a mymonad expression. 我编写monad的方式是调用方始终可以使用Async.RunSynchronously运行mymonad表达式。

UPDATE 2: 更新2:

So far I ended up with the following: 到目前为止,我最终得到以下结果:

  1. I use an explicit type for each member of MyMonadBuilder 我为MyMonadBuilder的每个成员使用显式类型
  2. I added ReturnFrom to the MyMonadBuilder. 我将ReturnFrom添加到MyMonadBuilder。 I use this function to wrap an Async< Result<'t> > 我使用此函数包装Async <Result <'t>>
  3. I added helper functions like failwith which create a mymonad with the error value 我添加了诸如failwith之类的辅助函数,它创建了一个带有错误值的mymonad

The code looks like this: 代码如下所示:

type MyMonad<'t> = 't Result Async

type MyMonadBuilder() =
    member x.Bind<'t> (v,f) : MyMonad<'t>= 
        async { 
            let! r = v
            match r with
            | Ok r' -> return! f r'
            | Error msg -> return Error msg
        }

    member x.Return<'t> v  : MyMonad<'t> = async {return Ok v }
    member x.ReturnFrom<'t> v  : MyMonad<'t> = v

    member x.Delay(f) = f()

let failwith<'t> : string -> MyMonad<'t> = Result.Error >> async.Return

This looks reasonably good for my purpose. 对于我的目的,这看起来相当不错。 Thanks! 谢谢!

Asynchronous workflows automatically support error handling through exceptions, so the idiomatic solution is to just use exceptions. 异步工作流通过异常自动支持错误处理,因此惯用解决方案只是使用异常。 If you want to distinguish some special kind of errors, then you can just define a custom exception type: 如果要区分某些特殊类型的错误,则可以只定义自定义异常类型:

exception MyError of string

// Workflow succeeds and returns 1000
let a = async { return 1000 }
// Workflow throws 'MyError' exception
// (using return! means that it can be treated as a workflow returning int)
let b = async { return! raise (MyError "some message") }

// Exceptions are automatically propagated
let sum = async {
  let! r1 = a
  let! r2 = b
  return r1 + r2 }

If you want to handle exceptions, you can use try ... with MyError msg -> ... inside an asynchronous workflow. 如果要处理异常,可以在异步工作流中使用try ... with MyError msg -> ...

You could define a custom computation builder that re-implements this using an algebraic data type such as your Result , but unless you have some really good reason for doing that, I would not recommend this approach - it will not work with standard libraries, it is quite complicated and does not fit with the general F# style. 可以定义一个自定义计算生成器,以使用诸如Result类的代数数据类型重新实现此功能,但是除非您有充分的理由这样做,否则我不建议您使用这种方法-它不适用于标准库,很复杂,不适合一般的F#风格。

In your computation expression, the type of values is Async<Result<'T>> , return automatically wraps the argument of type 'T in an async workflow that returns Ok . 在您的计算表达式中,值的类型为Async<Result<'T>>return自动将类型'T的参数包装在返回Ok的异步工作流中。 If you wanted to construct a value representing a failure, you can use return! 如果要构造表示失败的值,可以使用return! and create an async workflow that returns Result.Error . 并创建一个返回Result.Error的异步工作流。 You probably need something like this: 你可能需要这样的东西:

let c = mymonad { 
  return! async.Return(Result.Error "Some message")
}
let d = mymonad {
    return 1000
} 

But as I said, using exceptions is a better approach. 但正如我所说,使用例外是一种更好的方法。

EDIT: To answer the question in the comments - if you have a number of async computations, you can still wrap the final result in your custom type. 编辑:要在注释中回答问题-如果您有许多异步计算,您仍然可以将最终结果包装在您的自定义类型中。 However, you do not need to rebuild the entire asynchronous workflow library - errors in the primitive operations can still be handled using standard exceptions: 但是,您不需要重建整个异步工作流库 - 仍可以使用标准异常处理基元操作中的错误:

// Primitive async work that may throw an exception
let primitiveAsyncWork = async { ... } 

// A wrapped computation that returns standard Option type
let safeWork = async {
  try 
    let! res = primitiveAsyncWork
    return Some res
  with e -> return None }

// Run 10 instances of safeWork in parallel and filter out failed computations
async { let! results = [ for i in 0 .. 9 -> safeWork ] |> Async.Parallel
        return results |> Seq.choose id }

The asyncChoice workflow from my ExtCore library already implements this -- it's available on NuGet so all you need to do is add a reference to your project, open the ExtCore.Control namespace in your source files, and start writing code like this: 我的ExtCore库中的asyncChoice工作流已经实现了-在NuGet上可用,因此您要做的就是添加对项目的引用,在源文件中打开ExtCore.Control命名空间,并开始编写如下代码:

open ExtCore.Control

let asyncDivide100By (x : int) =
    asyncChoice {
        if x = 0 then
            return! AsyncChoice.error "Cannot divide by zero."
        else
            return (100 / x)
    }

let divide100By (x : int) =
    let result =
        asyncDivide100By x
        |> Async.RunSynchronously

    match result with
    | Choice1Of2 result ->
        printfn "100 / %i = %i" x result
    | Choice2Of2 errorMsg ->
        printfn "An error occurred: %s" errorMsg


[<EntryPoint>]
let main argv =
    divide100By 10
    divide100By 1
    divide100By 0

    0   // Exit code

asyncChoice is constructed using the standard Async<'T> and Choice<_,_> types from the F# Core library, so you shouldn't have any compatibility problems. asyncChoice是使用F#Core库中的标准Async<'T>Choice<_,_>类型构造的,因此您不会出现任何兼容性问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM