fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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