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.