Add AsyncResult wrapper type to help type inference
cmeeren opened this issue · 3 comments
We might want to go away from the Async<Result<_,_>>
overloads and instead use a dedicated wrapper type:
Type AsyncResult<'a,'b> = AR of Async<Result<'a,'b>>
This is similar to how Chessie works. The main benefit is to avoid having to add type annotations with let!
and use!
(dotnet/fsharp#4472).
Pros:
- No more need for type annotations on
let!
bindings whenever the right-hand side isAsync<Result<_,_>>
- No more need for type annotations after
use!
bindings (same aslet!
, but slightly more cumbersome sinceuse!
doesn't support type annotations on the binding, i.e.use! (foo: MyDisposableType) = ...
is invalid and type annotations must be added later where it's first used) - No more trickery with extension overloads (I don't really have a problem with this in and of itself)
Cons:
- Breaking change (not important, we're still at 0.x and the library is less than a week old)
- Right hand sides having type
Async<Result<_,_>>
must be wrapped inAR
by appending|> AR
(though this is not very likely to happen, since such right-hand sides will likely be the result of anotherasyncResult
CE and already be of typeAsyncResult<_,_>
) - When using an
AsyncResult
in anasync
CE (in order to get the result, or at high levels of the app), it must be unwrapped toAsync<Result<_,_>>
I don't like it.
The type inference issue only appears when the first thing you do with the unwrapped value is invoking one of its members. But if you're going as far as to use this library, it's much more likely that you'll be writing in a more functional style and passing the result to a function - in which case type inference works fine.
This change, however, would introduce extra complexity to every usage of the asyncResult
builder, whether it would have encountered the issue or not. And it's confusing that "result
is just syntactic sugar for Result<_,_>
" and "async
is just syntactic sugar for Async<_,_>
", but "asyncResult
is actually a reified wrapper around Async<Result<_,_>>
".
Also, if you run into the need to manually wrap/unwrap the type for whatever reason, it's not intuitive why you need to do |> AR
or let (AR x) = ..
. On the other side, having to add a type annotation to help the compiler is something pretty much every F# user has encountered at some point, so it won't be an unfamiliar situation.
Thanks, I appreciate the input. I realize I'm colored by how I've had to use the CE in a part-legacy project; I often access members of unwrapped objects, but as you say, that won't be a problem in other cases.
Furthermore, this is something that could (and IMHO should) be fixed at the language level, cf. dotnet/fsharp#4472 (whether it will ever be done is another matter, of course).