Top level F# progams - implicit async and access to command-line args
bugproof opened this issue · comments
Top level F# progams - implicit async and access to command-line args
See dotnet/fsharp#11631 and https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements for more detailed explanation
In C# 9 we can write code such as this
using System.Net.Http
using var httpClient = new HttpClient();
var str = await httpClient.GetStringAsync("https://example.com/");
Console.WriteLine(str);
You can also access args
from top level statements which you can't do from F#.
The existing way of approaching this problem in F# is:
There's no good way. See this comment dotnet/fsharp#11631 (comment)
Pros and Cons
The advantages of making this adjustment to F# are: shorter, easier code. No need to remember code such as this
let theAsyncTask : Async<int> = ...
[<EntryPoint>]
let main argv =
async {
do! Async.SwitchToThreadPool ()
return! theAsyncTask
} |> Async.RunSynchronously
The disadvantages of making this adjustment to F# are: I don't see real disadvantages.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): I don't know what the cost is.
Related suggestions: dotnet/fsharp#11631
Affidavit (please submit!)
Please tick this by placing a cross in the box:
- This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
- I have searched both open and closed suggestions on this site and believe this is not a duplicate
- This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
- This is not a breaking change to the F# language design
- I or my company would be willing to help implement and/or test this
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.
Do we want an implicit async
context or a task
/backgroundTask
context as specified in dotnet/fsharp#6811? Moreover, you can get the equivalent of args
with System.Environment.GetCommandLineArgs().[1..]
(the first one is the program path and name which the C# args
omit implicitly). Will it be too implicit to provide args
without a definition?
Personally, I think that having an implicit async (or really, any implicit computation expression) in an implicit entry point is not a good idea, especially when noted above that there are multiple ways to represent an asynchronous operation (Async and Task), as well as potentially multiple different computation expression builders which can be used to build them. Rather, I think it would make more sense to determine what return value is used (i.e. the type of the last expression in the top level statements) to determine what type of entry point is created. If it is an int
, wrap it in a regular entry point. If it is an int async
, wrap it in an Async
returning function, and have the generated entry point call the function passing the result into Async.RunSynchronously
. If it is a Task<int>
, do the same, except call GetAwaiter().GetResult()
instead. Also, maybe allow unit
types as well, as it appears this is how implicit entry points are handled currently.
Implemented this way, you would still need to wrap the code in async { ... }
or task { ... }
or whatever (or, at least, make sure the last statements are wrapped as such), but you would not need to perform the extra ceremony of applying Async.RunSynchronously
or GetAwaiter().GetResult()
or whatever else.
Implemented this way, you would still need to wrap the code in async { ... } or task { ... } or whatever (or, at least, make sure the last statements are wrapped as such), but you would not need to perform the extra ceremony of applying Async.RunSynchronously or GetAwaiter().GetResult() or whatever else.
Which will in the end give you as much benefit as C#'s async main. That is to say, save you a handful of characters every time you need to write main from scratch. How often do you do that, how much typing/time will this realistically save? I'm not against this, but I feel the perceived benefit would be very low.
I think we have a pretty good escape hatch in the form of FSI for what seems to me to be a primarily c# problem: having to make a full project for tiny executables to try things out, which leads to a certain amount of boilerplate overhead for each of these tiny executables.
Well, I really like this feature in C# and it really simplifies code for small tools/scripts. The compiler detects what you use in top-level statements and generates appropriate main for you https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements#implicit-entry-point-method
Also see this dotnet/csharplang#2765
I would personally favor a succinct convention for getting the arguments, but I would not prefer an implicit async or task-based context. That complicates things for C# today, because it forces all type definitions to come at the bottom of the file. Whereas not having that makes the experience mostly the same today in current F#.
Another thing to consider is that this might be more of an educational problem for F# than anything else. If the answer to the scenarios where C# top-level projects are used is to use an F# script, and it's not obvious, then that should be addressed somehow.
A succinct convention for getting the arguments:
let args = System.Environment.GetCommandLineArgs().[1..]
I would argue that's not terribly succinct. I realize it's the .NET way of doing sys.argv
, but that's a pretty rough line of code. fsi.CommandLineArgs
in scripts is a great way to do it, hence my comment about scripts and discovering they're the right choice for a scenario like this.
One approach could be to alias args
in FSharp.Core. It would take care of handling name resolution correctly, but could also be confusing since it's such a common name for things in user code.
cmdArgs
or cmdLineArgs
is an alternative to args
.
I dislike the implicit async
too, it could be a Task or Async or any other thing
But I think that a succinct args
and maybe a default [<EntryPoint>]
would be nice