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()
}
Related
What is the best practice is to "register" the http client in one place, so it can be reused from this Elmish update function? Instead of having to create it for every request.
let update message model =
match message with
| SetMessage s -> { model with x = s }
| Loading -> { model with x = "Doing something long ..." }
let handleClick model dispatch _ =
dispatch Loading
async {
let url = Uri "https://api.github.com"
-- FIXME: too expensive to do this on per-update basis
use httpClient = new HttpClient(BaseAddress = url)
let! resp = httpClient.GetAsync "/users/srid" |> Async.AwaitTask
let! s = resp.Content.ReadAsStringAsync() |> Async.AwaitTask
dispatch (SetMessage s)
} |> Async.Start
I feel like this would normally go in Startup.fs. I use a client-only Bolero web app, so this would look like:
builder.Services.AddSingleton<HttpClient>(new HttpClient (BaseAddress=apiBase))
But then the question becomes ... how do I access it from my program in F#? What is the idiomatic way?
Probably the best way would either be to add HttpClient as another field in your model or as another parameter to your update function.
let update (client:HttpClient) message model = // Your code
let url = Uri "https://api.github.com"
let httpClient = new HttpClient(BaseAddress = url)
In general you shouldn't "do work" in your view and, by extension, event handlers. Instead, you should use the Elmish Cmd module something like this:
let update httpClient message model =
match message with
| SetMessage s ->
{ model with x = s }, Cmd.none
| GetMessageAsync ->
let cmd =
let getHttp () =
async {
let! resp = httpClient.GetAsync "/users/srid" |> Async.AwaitTask
return! resp.Content.ReadAsStringAsync() |> Async.AwaitTask
}
Cmd.OfAsync.perform getHttp () (fun s -> SetMessage s)
{ model with x = "Doing something long ..." }, cmd
let handleClick model dispatch _ =
dispatch GetMessageAsync
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
}
I've created a website using WebSharper and has stumbled into a problem. I wish to integrate the site with VSTS REST API. To do that (seemlessly) I need to forward a session cookie. How do I do that in an WebSharper-Ajax call. My current implementation of the Ajax call prior to needing this looks like this and works just fine for the other needs I've had so far
let Ajax (request : Request) =
let httpMethod = request.Method
let url = request.EndPoint
let data = request.AsJson
let success ok =
System.Action<obj,string,JqXHR>(
fun res _ _ ->
let result = (res :?> string |> Json.Parse)
if JS.HasOwnProperty result "error" then
{
ErrorType = result?error
Reason = result?reason
} |> pushError
else
result
|> Success
|> ok
)
let contentType = Union<bool,string>.Union2Of2("application/json")
try
Async.FromContinuations
<| fun (ok, ko, _) ->
let settings = JQuery.AjaxSettings(
Url = url,
DataType = JQuery.DataType.Text,
Type = As<JQuery.RequestType> httpMethod,
Success = success ok,
ContentType = contentType,
Error = System.Action<JqXHR,string,string>(fun jqXHR _ _ ->
let error =
jqXHR?responseText
|> Json.Parse
{
ErrorType = error?error
Reason = error?reason
} |> pushError |> ok
)
)
match data with
Some data ->
settings.Data <- data
| None -> ()
JQuery.Ajax(settings) |> ignore
with e ->
async {
return {
ErrorType ="uncaught exception";
Reason = e.Message
} |> Error
}
It turns out that the solution is pretty easy. After creating the AjaxSetting object, simply use dynamic typing to add the xhrFields object
settings?xhrFields <- obj()
settings?xhrFields?withCredentials <- true
How do I get items from an RSS feed using .Net Core?
The following code doesn't appear to work:
open Microsoft.SyndicationFeed
open Microsoft.SyndicationFeed.Rss
[<Test>]
let ``Get links from iTunes RSS Feed`` () =
let url = "http://www.pwop.com/feed.aspx?show=dotnetrocks&filetype=master&tags=F%23"
use reader = XmlReader.Create(url)
let feedReader = RssFeedReader(reader)
let mutable linkTemplate = {
Title= ""
Url= ""
}
let links =
async {
let links = Collections.Generic.List<Link>()
while feedReader.Read() |> Async.AwaitTask |> Async.RunSynchronously do
match feedReader.ElementType with
| SyndicationElementType.Link ->
let item = feedReader.ReadLink() |> Async.AwaitTask |> Async.RunSynchronously
let link = { linkTemplate with Title= item.Title; Url= item.Uri.AbsolutePath }
links.Add(link)
| _ -> ()
return links
} |> Async.RunSynchronously
reader.Close()
System.Diagnostics.Debug.WriteLine(links.[0].Title)
links.[0].Title |> should not' (equal "")
Specifically, items are read but there's no actual data after the read.
I used the XElement class as recommended:
[<Test>]
let ``Get links from iTunes RSS Feed`` () =
let toLink (item:XElement) = {
Id = -1
ProfileId = "to be derived..."
Title= item.Element(XName.Get("title")) |> string
Url= item.Element(XName.Get("link")) |> string
Description = item.Element(XName.Get("description")) |> string
ContentType= Podcast |> contentTypeToString
Topics = []
IsFeatured= false
}
let baseAddress = "http://www.pwop.com/"
let url = "feed.aspx?show=dotnetrocks&filetype=master&tags=F%23"
use client = httpClient baseAddress
let response = client.GetAsync(url) |> Async.AwaitTask
|> Async.RunSynchronously
let links =
if response.IsSuccessStatusCode
then let text = response.Content.ReadAsStringAsync() |> Async.AwaitTask |> Async.RunSynchronously
XElement.Parse(text).Descendants(XName.Get("item")) |> Seq.toList |> List.map toLink
else []
links |> List.isEmpty |> should equal false
Considering the following
type MyClass () =
member x.ReadStreamAsync(stream:Stream) =
async {
let tcs = new TaskCompletionSource<int>()
let buffer = Array.create 2048 0uy
let! bytesReadCount = stream.ReadAsync(buffer, 0, buffer.Length) |> Async.AwaitTask
if bytesReadCount > 0 then
for i in 0..bytesReadCount do
if buffer.[i] = 10uy then
tcs.SetResult(i)
// Omitted more code to handle the case if 10uy is not found..
return tcs.Task
}
The code reads from a stream until in meets a certain character (represented by a byte value) at which point the task returned by the method completes.
The function signature of DoSomethingAsync is unit -> Async<Task<int>>, but I would like it to be unit -> Task<int> such that it can be used more generally in .NET.
Can this be done in F# using an asynchronous expression, or do I can to rely more on the Task constructs of .NET?
Given that you don't actually use the async workflow for anything in your example, the easiest solution would be to forgo it entirely:
member x.DoSomethingAsync() =
let tcs = new TaskCompletionSource<int>()
Task.Delay(100).Wait()
tcs.SetResult(10)
tcs.Task
This implementation of DoSomethingAsync has the type unit -> Task<int>.
It's not clear to me exactly what you're trying to do, but why don't you just do the following?
member x.DoSomethingAsync() =
async {
do! Async.Sleep 100
return 10 } |> Async.StartAsTask
This implementation also has the type unit -> Task<int>.
Based on the updated question, here's a way to do it:
member x.DoSomethingAsync(stream:Stream) =
async {
let buffer = Array.create 2048 0uy
let! bytesReadCount =
stream.ReadAsync(buffer, 0, buffer.Length) |> Async.AwaitTask
if bytesReadCount > 0
then
let res =
[0..bytesReadCount]
|> List.tryFind (fun i -> buffer.[i] = 10uy)
return defaultArg res -1
else return -1
}
|> Async.StartAsTask
The DoSomethingAsync function has the type Stream -> System.Task<int>. I didn't know what to do in the else case, so I just put -1, but I'm sure you can replace it with something more correct.