I have a method that conditionally calls an async task:
let processMsg (msg : SocketMessage) =
task{
//....
//....
let user =
if msg.User.IsSpecified && (msg.User.Value :? IUser) then
user.Value :?> IUser
else
//Compiler: This construct may only be used within a computation expression
let! user' = rest.GetUserAsync(UserId, config.restOptions)
user'
//....
//....
}
The compiler is complaining that I have to be within a computation expression. I thought I was. The only way I could get it to work is to split it out into an async child method:
let getUserAsync (user : SocketUser) =
task{
if user.IsSpecified && (user.Value :? IUser) then
return user.Value :?> IUser
else
let! user' =
rest.GetUserAsync(UserId, config.restOptions)
return (user' :> IUser)
}
Now I have an additional async context just to handle a rare corner case. Is there a better way to handle this?
The inside of let user = ... is not a computation expression. The let itself is inside a computation expression, but whatever is inside it is not. So you can't use let! in there.
The way to do what you're trying to do is to turn that let into a let! and then make sure that its right-hand side is a Task:
let! user =
if msg.User.IsSpecified && (msg.User.Value :? IUser) then
Task.FromResult(user.Value :?> IUser)
else
rest.GetUserAsync(UserId, config.restOptions)
Related
I use thirtyfour in my Rust script, and it uses tokio as the async runtime.
When I use find in a Vec::iter, it doesn't work as I expect:
#[tokio::main]
async fn main() -> WebDriverResult<()> {
let dropdown = driver.find_element(By::Tag("select")).await?;
dropdown
.find_elements(By::Tag("option"))
.await?
.iter()
.find(|&&x| x.text() == book_time.date) // Error, x.text() return a futures::Future type while book_time.date return a &str.
.click()
.await?;
}
After I tried Ibraheem Ahmed's solution, I met more errors:
let dropdown = driver.find_element(By::Tag("select")).await?;
let elements = dropdown.find_elements(By::Tag("option")).await?;
let stream = stream::iter(elements);
let elements = stream.filter(|x| async move { x.text().await.unwrap() == target_date });
error: lifetime may not live long enough
--> src\main.rs:125:38
|
125 | let elements = stream.filter(|x| async move { x.text().await.unwrap() == target_date });
| -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| ||
| |return type of closure is impl futures::Future
| has type `&'1 thirtyfour::WebElement<'_>`
There's a good thread on the Rust User's Forum that covers a similar question.
I tried the code snippets below by modifying the thirtyfour tokio_async example. I didn't have your full context, so I created an example that found a link based on its text on the wikipedia.org home page.
filter_map.
let target_value = "Terms of Use";
let found_elements: Vec<_> = stream
.filter_map(|x| async move {
if let Ok(text) = x.text().await {
if text == target_value {
println!("found");
return Some(x);
}
}
None
})
.collect()
.await;
while loop (which is probably not what you are after, but could be a simple solution if your logic fits easily inside)...
while let Some(element) = stream.next().await {
let text = element.text().await?;
println!("link text result: {}", text);
}
You can use a Stream, which is the asynchronous version of Iterator:
use futures::stream::{self, StreamExt};
fn main() {
// ...
let stream = stream::iter(elements).await?;
let elements = stream
.filter(|x| async move { x.text().await.as_deref() == Ok(book_time.date) })
.next()
.click()
.await?;
}
Is there a way to properly tell flow that I'm returning a function with the same signature as the function I'm passed, but not exactly the same function ?
This is an example of a "once" wrapper which prevents a function from being called multiple times, it works but uses an any-cast internally to make flow give up, I'd like to get rid of that cast and have 100% coverage:
module.exports.once = /*::<F:Function>*/(f /*:F*/) /*:F*/ => {
let guard = false;
return ((function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
}/*:any*/) /*:F*/);
};
Okay, first things first.
Your return value can currently never match F without your casting through any because the signature of the function you're returning is not the same because it can return undefined where the original may not.
(comment syntax removed for readability)
module.exports.once = <F: Function>(f: F): F => {
let guard = false;
return ((function () { // this function returns the return value of F or void
if (guard) { return; } // returning void
guard = true;
return f.apply(null, arguments);
}: any): F);
};
But to start typing this, we're gonna need to break down that function generic a little bit.
First of all, let's not use Function as it's generally better if we don't:
However, if you need to opt-out of the type checker, and don’t want to go all the way to any, you can instead use Function. Function is unsafe and should be avoided.
Also, we're going to extract the types of the arguments and the return value so we can manipulate them independently and construct a return type. We'll call them Args and Return so they're easy to follow.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
) ((...Array<Args>) => Return | void) => { // note `Return | void`
let guard = false;
return function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
};
};
Now that we're taking into account that our new function might return void everything type checks fine. But of course, the return type of our once function will no longer match the type of the passed function.
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once(func); // error!
// Cannot assign `module.exports.once(...)` to `onceFunc` because
// undefined [1] is incompatible with string [2] in the return value.
Makes sense, right?
So, let's discuss the signature of this function. We want our return value to have the same signature as the function we pass in. Currently it doesn't because we're adding void to the signature. Do we need to? Why are we returning undefined? How can we always return the same type from our onced function? Well, one option would be to store the return value from the single call to the function and always return the stored return value for subsequent calls. This would kind of make sense because the whole point is to allow multiple calls but not perform any of the functions effects. So this way we can avoid changing the interface of the function, so we really don't need to know whether or not the function has been called before.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
): ((...Array<Args>) => Return) => {
let guard = false;
let returnValue: Return;
return function () {
if (guard) { return returnValue; }
guard = true;
returnValue = f.apply(null, arguments);
return returnValue;
};
};
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once2(func);
One good question to ask at this point would be, why do the types match even if we're not technically returning exactly F? The answer to that is because functions in flow are structurally typed. So if they have the same arguments and return value, their types match.
I am running a simple chat app with f#. In the chat when one user types "exit" then I want both clients to finish the chat. Currently I am running in the console, and so read and write are blocking, but I am using a class to wrap the console so there is no async problems.
(In the following code the sendUI and reciveUI are async functions that send and recieve messages over the wire)
type IConnection =
abstract Send : string -> Async<bool>
abstract Recieve : unit -> Async<string>
abstract Connected : bool
abstract Close : unit -> unit
type IOutput =
abstract ClearLine : unit -> unit
abstract ReadLine : ?erase:bool -> string
abstract WriteLine : string -> unit
let sendUI (outputer:#IOutput) (tcpConn: #IConnection) () =
async {
if not tcpConn.Connected then return false
else
let message = outputer.ReadLine(true)
try
match message with
| "exit" -> do! tcpConn.Send "exit" |> Async.Ignore
return false
| _ -> if message.Trim() <> ""
then do! message.Trim() |> tcpConn.Send |> Async.Ignore
outputer.WriteLine("me: " + message)
return true
with
| e -> outputer.WriteLine("log: " + e.Message)
return false
}
let recieveUI (outputer:#IOutput) (tcpConn: #IConnection) () =
async {
if not tcpConn.Connected then return false
else
try
let! response = tcpConn.Recieve()
match response with
| "exit" -> return false
| _ -> outputer.WriteLine("other: " + response)
return true
with
| e -> outputer.WriteLine("error: " + e.Message)
return false
}
let rec loop (cancel:CancellationTokenSource) f =
async {
match! f() with
| false -> cancel.Cancel(true)
| true -> do! loop cancel f
}
let messaging recieve send (outputer: #IOutput) (tcpConn:#IConnection) =
printfn "write: exit to exit"
use cancelSrc = new CancellationTokenSource()
let task =
[ recieve outputer tcpConn
send outputer tcpConn ]
|> List.map (loop cancelSrc)
|> Async.Parallel
|> Async.Ignore
try
Async.RunSynchronously (computation=task, cancellationToken=cancelSrc.Token)
with
| :? OperationCanceledException ->
tcpConn.Close()
let exampleReceive =
{ new IConnection with
member this.Connected = true
member this.Recieve() = async { do! Async.Sleep 1000
return "exit" }
member this.Send(arg1) = async { return true }
member this.Close() = ()
}
let exampleOutputer =
{ new IOutput with
member this.ClearLine() = raise (System.NotImplementedException())
member this.ReadLine(erase) = Console.ReadLine()
member this.WriteLine(arg) = Console.WriteLine(arg) }
[<EntryPoint>]
let main args =
messaging recieveUI sendUI exampleOutputer exampleReceive
0
(I wrapped the console with an object so i wont get weird things on screen: outputer)
When I get "exit" over the wire i return false and so the loop calls cancel so it should also stop the sending messages async computation.
However, when I do this, the sendUI gets stuck:
async {
//do stuff
let message = Console.ReadLine() //BLOCKS! doesn't cancel
//do stuff
}
One fix would be to somehow make Console.ReadLine() an async, however the simple async { return ...} does not work.
I also tried running it as a task and calling Async.AwaitTask, but this does not work either!
I read that one can use Async.FromContinuations but I couldn't figure out how to use it (and what I tried didn't solve it...)
Little help?
EDIT
The reason this doesn't simply work is because the way async computations cancellation work. They check whether to cancel when it reaches a let!/do!/return! etc, and so the solutions above do not work.
EDIT 2
Added runnable code sample
You can wrap the Console.ReadLine in its own async, then call that with Async.RunSynchronously and a CancellationToken. This will allow you to cancel that blocking operation, because it won't be on the same thread as the console itself.
open System
open System.Threading
type ITcpConnection =
abstract member Send: string -> unit
let readLineAsync cancellation =
async {
try
return Some <| Async.RunSynchronously(async { return Console.ReadLine() }, cancellationToken = cancellation)
with | _ ->
return None
}
let receiveUI cancellation (tcpConnection: ITcpConnection) =
let rec loop () =
async {
let! message = readLineAsync cancellation
match message with
| Some msg -> msg |> tcpConnection.Send
| None -> printfn "Chat Session Ended"
return! loop ()
}
loop () |> Async.Start
I'm a little confused on how to do this seemingly simple task. I'm using WSDL Type Provider. I want to call one request, and if successful, call another and return the result:
let submitAll answers info = async {
let! answerResult = ws.AddAnswersAsync(info.id, answers) |> Async.AwaitIAsyncResult
return!
if answerResult
then ws.SubmitInfo(info) |> Async.AwaitIAsyncResult
else false
}
So, I get that I'm returning two types. But I'm not sure what to do with the false, is there an easy way to make it async?
Is there a better way to write this kind of asynchronous flow?
let submitAll answers info = async {
let! answerResult = ws.AddAnswersAsync(info.id, answers) |> Async.AwaitIAsyncResult
return!
if answerResult
then ws.SubmitInfo(info) |> Async.AwaitIAsyncResult
else async {return false}
}
Is there a difference between writing something like this:
MailboxProcessor.Start(fun inbox -> async {
let rec loop bugs =
let! msg = inbox.Receive()
let res = //something
loop res
loop []})
And writing it like this:
MailboxProcessor.Start(fun inbox ->
let rec loop bugs = async {
let! msg = inbox.Receive()
let res = //something
do! loop res }
loop [])
Thanks!
The first example is not valid F# code, because let! can only be used immediately inside computation expression. In your example, you're using it in an ordinary function - its body is not a computation expression, so let! is not allowed in that position.
To make it valid, you'd need to wrap the body of the loop function inside async:
MailboxProcessor.Start(fun inbox -> async {
let rec loop bugs = async {
let! msg = inbox.Receive()
let res = //something
return! loop res }
return! loop []})
You can keep the outer async { .. } block in the snippet as well - then you just need to use return! to call your loop function instead of just returning it (but other than that there is no significant difference now).
Note that I used return! instead of do! - this actually makes a difference, because return! represents a tail-call, which means that the rest of the current body can be discarded. If you use do! then the async allocates something like a stack frame in the heap, so using do! in a recursive looping function leaks memory.