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:
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:
...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. 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. The way I wrote the monad the caller can always use Async.RunSynchronously to run a mymonad expression.
UPDATE 2:
So far I ended up with the following:
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.
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.
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
. If you wanted to construct a value representing a failure, you can use return!
and create an async workflow that returns 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:
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.