Why can't I use let! (let bang) inside a try/with expression? - f#

I'm making a call to an async function from inside a task {...} computation expression. I can normally use let! to call async functions and await their results. However, if I move the function call into a try/with expression, I get the following compiler error.
This construct may only be used within computation expressions
Since this code is still within the task{...} expression, I'm not sure what the compiler means exactly.
In the following contrived example the call to getDataMayThrowAsync from controller1 compiles, but in controller2 I get the above compile error.
module LetBangIssue =
open System
open System.Threading.Tasks
let getDataMayThrowAsync (id:int) = task {
do! Task.Delay(10)
let random = Random()
if random.Next(1, 1000) < 500 then
raise <| Exception "Something went wrong"
return "the data"
}
// Compiles, but doesn't handle error case...
let controller1() = task {
let! tryGetTheData = getDataMayThrowAsync 10
return tryGetTheData
}
// Attempt to handle error case, but can't compile...
let controller2 () = task {
let theData =
try
let! tryGetTheData = getDataMayThrowAsync 10
tryGetTheData
with
| ex ->
printfn $"Error: {ex.Message}"
"couldn't get the data"
return theData
}
How can I fix controller2 to solve the compile error?

It's not try/with that's the problem. It's the extra let. Try this:
let controller2 () = task {
let theData =
let! tryGetTheData = getDataMayThrowAsync 10
tryGetTheData
return theData
}
This fails with the same error, because in the body of theData you're using let! without a surrounding task { }.
As written, the extra let is not needed at all, since you're just returning it at the end. So this would work fine:
let controller2 () = task {
try
let! tryGetTheData = getDataMayThrowAsync 10
return tryGetTheData
with
| ex ->
printfn $"Error: {ex.Message}"
return "couldn't get the data"
}
But if you really need the extra let, you have to make it a let! (because it's bound to an async result), you have to wrap its body in task (otherwise you can't use let! inside it), and you have to use return to return the values from it:
let controller2 () = task {
let! theData = task {
try
let! tryGetTheData = getDataMayThrowAsync 10
return tryGetTheData
with
| ex ->
printfn $"Error: {ex.Message}"
return "couldn't get the data"
}
return theData
}

Related

F# Fable Fetch Correctly Unwrap Promise to another Promise type

I need to request data from several URLs and then use the results.
I am using plain Fable 3 with the Fable-Promise and Fable-Fetch libraries.
I have worked out how to fetch from multiple URLs and combine the results into a single Promise that I can then use to update the UI (the multiple results need to be drawn only once).
But if one of the fetch errors then the whole thing falls over. Ideally I'd like to use tryFetch and then propagate the Result<TermData, None | Exception> but nothing I do seems to compile.
How exactly do I use tryFetch and then unwrap the result with a second let! in the CE? (The comments explain more)
module App
open Browser.Dom
open App
open System.Collections.Generic
open System.Text.RegularExpressions
open Fetch
open System
type TermData =
abstract counts : int []
abstract scores : int []
abstract term : string
abstract allWords : bool
type QueryTerm =
{ mutable Term: string
mutable AllWords: bool }
let loadSingleSeries (term: QueryTerm) =
promise {
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
// Works but doesn't handle errors.
let! plotData = fetch url [] // type of plotData: Response
// let plotDataResult = tryFetch url []
// This ideally becomes Promise<Result<TermData, None>>
// let unwrapped = match plotDataResult with
// | Ok res -> Ok (res.json<TermData>()) // type: Promise<TermData>
// | Error err -> ??? // tried Error (Promise.create(fun resolve reject -> resolve None)) among others
let! result = plotData.json<TermData>() // type of result: TermData
return result
}
let dataArrays =
parsed // type Dictionary<int, TermData>
|> Seq.map (fun term -> loadSingleSeries term.Value)
|> Promise.Parallel
|> Promise.map (fun allData -> console.log(allData))
// Here we will handle None when we have it
I don't have much Fable experience, but if I understand your question correctly, I think the following should work:
let loadSingleSeries (term: QueryTerm) =
promise {
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
let! plotDataResult = tryFetch url []
match plotDataResult with
| Ok resp ->
let! termData = resp.json<TermData>()
return Ok termData
| Error ex ->
return Error ex
}
The idea here is that if you get an error, you simply propagate that error in the new Result value. This returns a Promise<Result<TermData, Exception>>, as you requested.
Update: Fixed return type using a second let!.
I haven't run this code but looking at the docs it looks like you need to use Promise.catch
let loadSingleSeries (term: QueryTerm) =
promise {
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
let! plotDataResult =
fetch url []
|> Promise.map Ok // Wraps the "happy path" in a Result.Ok
|> Promise.catch (fun err ->
//handle the error
Error err)
return
match plotDataResult with
| Ok res -> ...
| Error err -> ...
}
I ended up having to use the pipeline rather than CE approach for this as follows:
let loadSingleSeries (term: QueryTerm) =
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
let resultPromise =
fetch url []
|> Promise.bind (fun response ->
let arr = response.json<TermData> ()
arr)
|> Promise.map (Ok)
|> Promise.catch (Error)
resultPromise
The key was using Promise.bind to convert the first promise to get the Response to the promise of Promise<TermData>. The map and catch then convert to a Promise<Result<TermData, exn>>.

f# timeout when using Async.RunSynchronously

I am new to f# and I have a question about timeout in asynchornization operations, this is simple communication using serial com ports, so I have a method/function:
let SendMessenge(port : Ports.SerialPort, messange: string) =
async {
do! port.AsyncWriteLine messange
let! response = port.AsyncReadLine() // this returns a string
return response
}
|> Async.RunSynchronously // the place i fiddled with
All communication (messengering) is controlled in this module:
type SerialPort with
member this.AsyncWriteLine(messange : string) = this.BaseStream.AsyncWrite(this.Encoding.GetBytes(messange + "\n"))
member this.AsyncReadLine() =
async {
let messange_builder = StringBuilder()
let buffer_ref = ref (Array.zeroCreate<byte> this.ReadBufferSize)
let buffer = !buffer_ref
let last_char = ref 0uy
while !last_char <> byte '\n' do
let! readCount = this.BaseStream.AsyncRead buffer
last_char := buffer.[readCount-1]
messange_builder.Append (this.Encoding.GetString(buffer.[0 .. readCount-1])) |> ignore
messange_builder.Length <- messange_builder.Length-1
let response : string = messange_builder.ToString()
printfn "Response: %s" response
return response
}
Basically this works fine, it sends a message and receives response, But now I want to add a timeout, in case i am connect. I tried to fiddle with
|> Async.RunSynchronously(???, timeout, cancel_token)
but with no luck. As i see in documentation it takes timeout and cancellation token and Async, What would be this T0 generic parameter in my case?
What happens is that Async.RunSynchronously is a static method, rather than a method function, so it takes its arguments with tuple syntax. So you can't partially apply it and pipe the last argument into it.
You can do this:
let response = async {
// ...
}
Async.RunSynchronously(response, timeout, cancel_token)
or if you really want to pipe:
async {
// ...
}
|> fun response -> Async.RunSynchronously(response, timeout, cancel_token)

Concurrent/Parallel Http calls in a Queue with throttle

If you have lots of URLs to call get on, are there any queue patterns in F# with some kind of limit, like let's say 5 or 10 calls at a time before proceeding to the next batch.
let urls = [
"http://example.com/1",
"http://example.com/2",
"http://example.com/3",
....
"http://example.com/100"]
While passing a function to call
let getAsync (url:string) =
async {
let httpClient = new System.Net.Http.HttpClient()
let! response = httpClient.GetAsync(url) |> Async.AwaitTask
response.EnsureSuccessStatusCode () |> ignore
let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return content
}
and then receive a list of all results and any errors since some calls may fail due to any reason like 404 or 500 errors.
First, make your function actually return the error information instead of throwing it as an exception:
let getAsync url : Result<_,_> =
async {
try
...
return (Ok content)
with ex ->
return (Error ex)
}
Then use Async.Parallel to execute them in parallel. This function takes an list<Async> and returns an Async<list>:
let allResults = urls |> List.map getAsync |> Async.Parallel

Awaiting an RX subject in F#

This is the same as How do I await a response from an RX Subject without introducing a race condition?, but in F#.
The C# solution looks like:
static async void Foo()
{
var subject = new Subject<int>();
var firstInt = subject.FirstAsync().PublishLast();
firstInt.Connect();
subject.OnNext(42);
var x = await firstInt;
Console.WriteLine("Done waiting: " + x);
}
My attempt in F# is this:
let foo () =
async {
use subject = new Subject<int>()
let firstInt = subject.FirstAsync().PublishLast()
firstInt.Connect() |> ignore
subject.OnNext(42)
let! x = firstInt
printfn "Done waiting: %d" x
return ()
}
The let x! = firstInt gives the compile error This expression was expected to have type Async<'a> but here has type IConnectableObservable<int> so apparently C# does something under the hood that F# doesn't.
Is there a C# implicit interface cast at work here, that I need to do explicitly in F#? If so, I can't figure out what it is.
After further digging, it seems that C# calls GetAwaiter() under the hood when you await something. For a Subject or an IObservable, GetAwaiter returns an AsyncSubject, which isn't immediately useful in F#, but the ToTask extension method in System.Reactive.Threading.Tasks makes it useful. Apparently, you can apply ToTask directly to a Subject (or an IObservable) without going by way of GetAwaiter, so my problem is solved by changing the let! x ... statement to:
let! x = firstInt.ToTask() |> Async.AwaitTask
edit:
There's a better way
Using FSharpx.Async is a much better way of accomplishing the same thing:
open FSharpx.Control.Observable
let foo () =
async {
use subject = new Subject<int>()
subject.OnNext(42)
let! x = Async.AwaitObservable subject
printfn "Done waiting: %d" x
return ()
}

How to handle AggregateException of a plain task (with FSharpx.Core)?

My question is a two-fold one, I believe. In the following, how to catch AggregateException and print it out when
The tasks are Task objects, not Task<void> ones?
When the tasks Task<_> ones (e.g. Task<void>?
Here's s short, somewhat fabricated snippet. The taskGenerator in the following snippet emulates some external C# library function that either returns an array of Tasks or one Task combined with Task.WhenAll.
I'm not sure how should I catch the resulting AggregateException and print it in either generic or non-generic case. Then other problem is the interaction with the non-generic one which I need to cast to satisfy FSharpx, but then Ignore throws the exception which may not be desired.The Ignore function is from here, and I believe it's a rather used way to transform a Task to Task<unit>.
open FSharpx
open System.Threading.Tasks
open System
open FSharpx.Task
[<EntryPoint>]
let main argv =
let Ignore(task:Task) =
let continuation (t: Task): unit =
if t.IsFaulted then
raise t.Exception
else
()
task.ContinueWith continuation
let taskGenerator() =
let task = TaskBuilder(scheduler = TaskScheduler.Default)
//The following just emulates an external source that returns a bunch of plain
//tasks. It could return also one plain task one would like to await for (e.g.
//this exernal, simulated source could call Task.WhenAll.)
task {
let tasks =
[|1; 2; 3;|]
|> Seq.map(fun i ->
let taskSource = new TaskCompletionSource<unit>()
taskSource.SetException(new ArgumentException("Argh!"))
taskSource.Task :> Task)
return! Task.WhenAll(tasks) |> Ignore
}
let final() =
let task = TaskBuilder(scheduler = TaskScheduler.Default)
task {
let! a = taskGenerator()
a |> ignore
}
try
let finalRes = final()
finalRes |> ignore
with :? AggregateException as aex ->
printfn "%A" aex
let x = System.Console.ReadLine()
0

Resources