Reputation: 22133
Why is the generic type constrained to unit, and how can I write this in a way that myFunc gets properly typed as unit -> Async<'t>
?
let myFunc (func: unit -> Async<'t>) = // myFunc: unit -> Async<unit>
async {
try
do! Async.Sleep 500
return! func() // 't constrained to unit here
with _ex ->
do! Async.Sleep 200
failwith "failed"
}
Edit: I was hoping this would make for a good minimal repro, but here's what I actually want to do:
let retryUntilTimeout (func: unit -> Async<'t>) timeout =
async {
let sw = Stopwatch.StartNew()
while sw.ElapsedMilliseconds < timeout do
try
return! func ()
with _ex ->
printfn "failed"
do! Async.Sleep 200
raise (TimeoutException())
}
Upvotes: 0
Views: 49
Reputation: 80744
Every expression must have a definite type, including a try ... with
expression. Having a definite type, in this case, means that both the try
and the with
branches must have the same type.
But in your code, the try
branch returns 't
, while the with
branch returns unit
. But no matter: since 't
can be anything, we can just unify it with unit
, and now the whole try ... with
expression returns unit
as well. Problem solved!
To fix this, you need to have the with
branch return a 't
as well. How to do that, where to get a 't
? I'm afraid I can't help you there, you must decide.
let myFunc (func: unit -> Async<'t>) =
async {
try
do! Async.Sleep 500
return! func()
with _ex ->
do! Async.Sleep 200
return someOtherValueOfT
failwith "failed"
}
But from the overall shape of the code, I suspect that what you really meant was to put the failwith
under the with
branch as well. Because failwith
can have any type you want, this will make the compiler happy with keeping the type of the whole try ... with
block as 't
:
let myFunc (func: unit -> Async<'t>) =
async {
try
do! Async.Sleep 500
return! func()
with _ex ->
do! Async.Sleep 200
return failwith "failed"
}
(note that there is now an extra return
keyword inside with
- that's because without a return
the async block is assumed to have type unit
, and we're back to the same problem)
Responding to your comments, it looks like what you're actually trying to do is an endless iteration, limited by a timeout, and you want to keep iterating as long as there is an error.
The problem with your code, however, is that it won't actually work the way you expect, even if you somehow return a value of 't
from the with
branch. This is because the return!
keyword doesn't "interrupt execution" like it does in C# (formally known as "early return"), but merely runs the given func()
and makes its value the result of the current block.
In fact, if you remove the with
branch entirely, the problem will persist: the compiler will still insist on the type of func()
being unit
. This is because the call is inside a while
loop, and the body of the while
loop must not return a value, otherwise that value effectively gets thrown away, so there must be a mistake of some sort. But it's ok to throw away a unit
, so the compiler allows it.
A good way to do what you're trying to do is via recursion:
let retryUntilTimeout (func: unit -> Async<'t>) timeout =
let sw = Stopwatch.StartNew()
let rec loop () = async {
if sw.ElapsedMilliseconds > timeout
then
return raise (TimeoutException())
else
try
return! func ()
with _ex ->
printfn "failed"
do! Async.Sleep 200
return! loop ()
}
loop ()
Here, the function loop
first checks the timeout and throws (note also the return
keyword - that's to satisfy the type system), otherwise runs func()
, but if that fails, waits a bit and calls itself recursively, thus continuing the process.
This general scheme (or stuff built on top of it) is how all iteration is modeled in functional programming. Forget about loops, they're not helpful.
Upvotes: 1