Not really sure why my code fails to compile, the error I am having:
Incomplete structured construct at or before this point in expression
let resendErrorsAsync (bus: IBus) (errorQueueName: string) =
async {
let! errorQueue = bus.Advanced.QueueDeclareAsync(errorQueueName) |> Async.AwaitTask
let! message = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
while message <> null do
let utf8Body = Encoding.UTF8.GetString(message.Body)
let error = JsonConvert.DeserializeObject<Error>(utf8Body)
let errorBodyBytes = Encoding.UTF8.GetBytes(error.Message)
let! exchange = bus.Advanced.ExchangeDeclareAsync(error.Exchange, "topic") |> Async.AwaitTask
let! message = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
}
It seems to be related to my two async calls in the while loop, not really sure why though.
You can't end a block with a let!, there has to be a body after it; that's the reason for the syntax error.
The message you're defining on the last line is a different variable to the one tested in the while condition. It seems you want to mutate the message; for that, you have to explicitly create a mutable variable:
let resendErrorsAsync (bus: IBus) (errorQueueName: string) =
async {
let! errorQueue = bus.Advanced.QueueDeclareAsync(errorQueueName) |> Async.AwaitTask
let! msg = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
let mutable message = msg
while message <> null do
let utf8Body = Encoding.UTF8.GetString(message.Body)
let error = JsonConvert.DeserializeObject<Error>(utf8Body)
let errorBodyBytes = Encoding.UTF8.GetBytes(error.Message)
let! exchange = bus.Advanced.ExchangeDeclareAsync(error.Exchange, "topic") |> Async.AwaitTask
let! msg = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
message <- msg
}
Related
I have function call
let result = session.Query<Domain.CustomerReadModel>().ToListAsync()
which returns Task<Collections.Generic.IReadOnlyList<Domain.CustomerReadModel>>
How do I "await" this task correctly in F#?
I tried
async {
session.Query<Domain.CustomerReadModel>().ToListAsync() |> ignore
}
as well as
let result = session.Query<Domain.CustomerReadModel>().ToListAsync()
|> Async.AwaitTask
|> Async.RunSynchronously
This seems to compile but I can't use the result and return it:
session.Query<Domain.CustomerReadModel>().ToListAsync()
|> Async.AwaitTask
|> Async.RunSynchronously
Async.AwaitTask takes the task, wraps it in an async computation, and returns you that computation. Once you have it, you can use it with let! or do! or return! just like any other async computation.
async {
let queryAsFsharpAsync = session.Query<Domain.CustomerReadModel>().ToListAsync() |> Async.AwaitTask
let! result = queryAsFsharpAsync
...
}
Or without giving the computation its own name:
async {
let! result = session.Query<Domain.CustomerReadModel>().ToListAsync() |> Async.AwaitTask
...
}
We are trying to use dapper to return multiple result sets in F#. However I keep getting an Invalid operation Exception. This is what we have set up:
let query<'T, 'T1> connStr sql =
async {
use connection = new SqlConnection(connStr)
do! connection.OpenAsync() |> Async.AwaitTask
use transaction = connection.BeginTransaction()
try
use! result =
connection.QueryMultipleAsync(sql.Query, dict sql.Parameters, transaction)
|> Async.AwaitTask
transaction.Commit()
let r1 = result.Read<'T>()
let r2 = result.Read<'T1>()
return (r1, r2)|> Ok
with
| ex ->
transaction.Rollback()
return ex |> DataException |> Error
}
The expected result would be a tuple of seqs, but currently I just keep getting
System.InvalidOperationException: 'There is already an open DataReader associated with this Command which must be closed first.'
The issue appears to be related to calling transaction.commit before read. Here is the correct implementation for this method:
let query<'T, 'T1> connStr sql =
async {
use connection = new SqlConnection(connStr)
do! connection.OpenAsync() |> Async.AwaitTask
use transaction = connection.BeginTransaction()
try
use! result =
connection.QueryMultipleAsync(sql.Query, dict sql.Parameters, transaction)
|> Async.AwaitTask
let r1 = result.Read<'T>()
let r2 = result.Read<'T1>()
// Move the commit after the reads
// Was before orignally
transaction.Commit()
return (r1, r2)|> Ok
with
| ex ->
transaction.Rollback()
return ex |> DataException |> Error
}
I tried the following code to implement do while in F#.
let listObjects bucketName = asyncSeq {
use client = new AmazonS3Client(RegionEndpoint.USEast2)
let request = new ListObjectsV2Request(BucketName = bucketName, MaxKeys = 10)
while (
let! response = client.ListObjectsV2Async(request) |> Async.AwaitTask
for entry in response.S3Objects do
yield entry.Key
response.IsTruncated) do ()
However, it cannot compile? Is the following code the only option?
let listObjects bucketName = asyncSeq {
use client = new AmazonS3Client(RegionEndpoint.USEast2)
let request = new ListObjectsV2Request(BucketName = bucketName, MaxKeys = 10)
let! response = client.ListObjectsV2Async(request) |> Async.AwaitTask
for entry in response.S3Objects do // do while
yield entry.Key
while response.IsTruncated do
let! response = client.ListObjectsV2Async(request) |> Async.AwaitTask
for entry in response.S3Objects do
yield entry.Key
You can generally use tail-recursion in F# to solve the same sort of problems you'd solve with loops in C#. I'm not entirely clear on what your intended while-loop behavior is, but here's an example of tail-recursivlely yielding the S3Objects until response.IsTruncated is false:
let listObjects bucketName =
asyncSeq {
use client = new AmazonS3Client(RegionEndpoint.USEast2)
let request = new ListObjectsV2Request(BucketName = bucketName, MaxKeys = 10)
let! response = client.ListObjectsV2Async(request) |> Async.AwaitTask
let rec getKeys () =
asyncSeq {
for entry in response.S3Objects do
yield entry.Key
if response.IsTruncated
then yield! getKeys ()
}
yield! getKeys()
}
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
I have the following interface method:
Task<string[]> GetBlobsFromContainer(string containerName);
and its implementation in C#:
var container = await _containerClient.GetContainer(containerName);
var tasks = container.ListBlobs()
.Cast<CloudBlockBlob>()
.Select(b => b.DownloadTextAsync());
return await Task.WhenAll(tasks);
When I try to rewrite it in F#:
member this.GetBlobsFromContainer(containerName : string) : Task<string[]> =
let task = async {
let! container = containerClient.GetContainer(containerName) |> Async.AwaitTask
return container.ListBlobs()
|> Seq.cast<CloudBlockBlob>
|> Seq.map (fun b -> b.DownloadTextAsync())
|> ??
}
task |> ??
I'm stuck with the last lines.
How to return to Task<string[]> from F# properly?
I had to guess what the type of containerClient is and the closest I found is CloudBlobClient (which does not have getContainer: string -> Task<CloubBlobContainer> but it shouldn't be too hard to adapt). Then, your function might look like as follows:
open System
open System.Threading.Tasks
open Microsoft.WindowsAzure.Storage.Blob
open Microsoft.WindowsAzure.Storage
let containerClient : CloudBlobClient = null
let GetBlobsFromContainer(containerName : string) : Task<string[]> =
async {
let container = containerClient.GetContainerReference(containerName)
return! container.ListBlobs()
|> Seq.cast<CloudBlockBlob>
|> Seq.map (fun b -> b.DownloadTextAsync() |> Async.AwaitTask)
|> Async.Parallel
} |> Async.StartAsTask
I changed the return type to be Task<string[]> instead of Task<string seq> as I suppose you want to keep the interface. Otherwise, I'd suggest to get rid of the Task and use Async in F#-only code.
Will this work?
member this.GetBlobsFromContainer(containerName : string) : Task<string seq> =
let aMap f x = async {
let! a = x
return f a }
let task = async {
let! container = containerClient.GetContainer(containerName) |> Async.AwaitTask
return! container.ListBlobs()
|> Seq.cast<CloudBlockBlob>
|> Seq.map (fun b -> b.DownloadTextAsync() |> Async.AwaitTask)
|> Async.Parallel
|> aMap Array.toSeq
}
task |> Async.StartAsTask
I had to make some assumptions about containerClient etc. so I haven't been able to test this, but at least it compiles.