-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Top level statements and member declarations (embrace scripting dialect) (VS 16.8, .NET 5) #2765
Comments
If the top-level members are only accessible within the current assembly (my preference) I'd be in favor of all top-level members being implicitly If you want these top-level members to be accessible between assemblies, you can take a cue from Kotlin which offers this same feature. It's similar to above but the name of the static class is predictable and based on the package/namespace. You can apply attributes/annotations to cause the compiler to emit a different type name if you want, which is useful for Java interop scenarios in Kotlin and I think would be similarly useful here with the .NET ecosystem. But to be honest, is it worth doing at all? Is there actually that much (or any) usage of C# script, enough to warrant that C# adopt it's syntax and idioms? To be honest I'd probably like to use it, but as far as I can tell it's not available on Mac. I've never seen it used in any Windows shop. To me it only functions as a slightly nicer Immediate Window. It could be more than that. |
At the risk of stating the obvious, how would this work if multiple source files in a single compilation declare top-level statements? |
@yaakov-h The simplest option would be to raise an error when there are multiple source files in a single compilation and at least one has top-level statements. |
(What follows is a brainstorm around the topic. I'm not invested in any of it - please feel free to shoot it down). I'm a little bit worried about allowing people to interleave top-level code with things like class definitions (although I know plenty of scripting languages allow this). I can imagine finding unexpected surprises hidden in larger applications. One thought is introducing something like a script
{
public int Fac(int x) => x < 0 ? x * Fac(x-1) : 1; // Top-level function
Console.WriteLine(Fac(5)); // Top-level statement
} (You could either allow top-level functions only inside a script block, or both inside and out). This makes it a bit more obvious that this code should be interpreted as a script. If there are features which are only available to top-level statements (I'm think of things like It also might make it a bit more intuitive that a script can't be split across multiple files (if that is indeed something that's forbidden). It might also make the error messages a bit nicer, since it gives an explicit name to a block of top-level code: "application already contains a 'script' definition" might be a bit clearer than "top-level code found in more than one file". One could also imagine being able to name scripts ( Once your script's got a name, that makes importing scripts across assemblies more intuitive. No attributes required. A natural step is then allowing files with a particular extension (e.g. Or maybe this is just piling on a lot of unnecessary complexity... Scripting languages get away with having top-level code in multiple files, because there's a clear "entry" file and a tree of files included recursively from that entry file in a deterministic and defined order. A file's top-level statements are run when that file is first included. C# doesn't have that luxury, so "which bit of top-level code is executed when" is always going to be a bit harder to explain. If we want C# to behave like a scripting language (in certain contexts), maybe we need to mimic this. That would mean giving script files a separate file extension (e.g. The downside is that there are now two distinct paradigms for people to learn, with the inevitable transition between them. There's also the challenge of explaining "this snippet of code only works if you put it in a file ending with |
I think what's more important for c# scripting than the syntax is the tooling. There are countless times when I would like to spin up a repl to test my C# code, but unfortunately I find the tooling for c# interactive to be lacking, and I'm forced to experiment via unit tests instead. |
@canton7 For all that C# lets you do, it is still a thoroughly unambiguous language; things like duplicate names are checked aggressively by the compiler more so than other languages. What could it be possible to screw up with top-level members that isn't possible to screw up without them? And aside from that, what utility would there be in keeping a script separate? Top level code isn't much of a sticking point, |
If we want to provide a smooth ramp-up for language learners, then I can envision the following approach:
That is, as soon as you want to declare a type or include a file, you need Main. |
Personally I like the idea of top-level functions and top-level const members. I think, top-level variables should be avoided. Also, instead of top level statements, I prefer a specific entry point or |
How about in addition to disallowing multiple files with top-level statements we also treat such statements as an implicit Main function? This way if user declares seperate Main, it will error out because of multiple declarations. I think top level function declarations will be fine spread across multiple files. |
Something else that might be useful for scripting would be treating the first line of a file as trivia if it starts with |
I wish we could write a top level function and member. But personally I don't like a top level statement. It would be ambiguous for which one will execute first in the project. While we could put one Top level statement should be limit to |
@MadsTorgersen I don't think this is desirable. In fact, we do have 3 dialects of C# language already, not two. We have C# proper, C# script and C# debug expressions. That last one is used in Expression Evaluators (Watch and Immediate windows). It has additional syntax and semantics that is implemented via customized binders. I believe we can unify C# script with C# debug expressions into a single dialect, at least on syntactic level, by bringing some of the C# debugging syntax extensions to C# script. For example, the C# debugging dialect allows for special identifiers starting with Re unification of C# proper and C# scripting: There are C# scripting features that wouldn't make much sense to merge into C# proper. For example, C# proper uses projects to list metadata references and all source files that are part of the compilation. Script C# uses So, I am very skeptical that we will be able to unify all these three dialects into one. That said, I think we can bring them closer together. |
@HaloFour raised the issue of whether C# script is really used much.
The main way I've used C# scripting is with Dave Glick's Scripty, which is a tool that allows C# scripts to act as an alternative to T4 templates. I find it much better to use than T4, and would actually like to see something like it become part of the compiler. Perhaps that's relevant to #12505. Templating and other build tools seem like a great usage for scripts. I like the idea of making this a native feature, but I think it should be disabled by default in each file unless explicitly enabled, either by a file-extension or pragma. |
We have API-level support for this kind of thing in the .NET kernel. It can be used by the host or by an extension author. It's very customizable and flexible enough to support different output types, e.g. plain text vs. HTML. This is an approach to custom output formatting for interactive coding that doesn't require language-level support. |
Sure, you can always call some pre-defined function that converts the result of the expression to some type that have a custom visualizer. However, the EE is already doing this via a pseudo-language feature ( |
Can you provide more details on this? I'm not familiar with it. |
Thank you @MadsTorgersen for this proposal written by you, because on github during long time was the fight for top-level statements and top-level functions !! I personally wrote proposal for top level function (#2156), but faced with huge pressure from some stubborn group of people ... :( I like this idea, because in this case it would be possible to use C# as replacement for Python and we will have the best from two worlds !! Again thank you @MadsTorgersen !! |
@MadsTorgersen This is a fantastic suggestion and would both be useful, and help gain the interest of a new generation in C#. I have two children (8 and 11) and would seriously consider introducing them to C# if this was implemented (instead of say Python or whatever they are learning at code club). It is just too unreasonable to expect children this age to grasp the large project benefits of formal object oriented programming and understand why the default template for a line of code starts like I have noted some commentators consider that usage of the C# scripting dialect has been relatively minor (and thus it might not be worth implementing this feature). This view is misguided as we have a chicken and egg problem. I have been programming in C# since the first alpha 19 years ago, and have always wanted something like this for 1-10 line programs. But other than "playing", I have never used the scripting dialects because:
The simplest implementation, but least flexible, would be to merely allow this syntax for the content of the Main method, such as shown below (everything after the using):
In this example "Greeting" and "Print" are not members of the class, but just defined inside the emitted Main method (thanks for these recent additions to C#). The class name could simply be the name of the file, so MyApp.cs would generate a class MyApp. Some people have commented about concerns of clashing with a previously declared Main method in the project. But how is that a problem? The compiler should just generate the error message it would already generate today (shown below):
I don't think you should add the script specific ways of referencing assemblies (and other similar extensions). The scripting environments can keep doing whatever they are doing in this respect. Remember there have always been various ways to reference assemblies. For example with csc.exe you might use /reference; or they can be referenced in a project file that is used by dotnet build. One request would be, can you make the entry point asynchronous, such as: Which leads to the next question: Should we, by convention, allow access to "args". I think you probably should to make this useful. Finally, I am not sure if you by default include any usings like This is exciting @MadsTorgersen and now we need a concrete proposal. |
This seems like a feature more appropriate for Visual Basic, which tends to attract learners and casual programmers. And speaking of which, is VB as a language still being developed? Or is it just in maintenance mode for legacy applications that need .NET Core support? |
I share the concerns about naming conflicts, but as long as C# remains a compiled language, I don't see a huge problem. This proposal would result in a huge reduction in boilerplate code in many cases. The C# compiler already aggressively checked for class name conflicts from different namespaces. |
More detailed proposal: #3117 |
On Windows, following the standard argv parsing convention is hard. https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way. Providing On other OSs, arguments are not passed between processes as a single string the way Windows does it. An array of separate strings is passed. It wouldn't make sense to join them into a single string. |
It would be trivial to add |
@Grauenwolf This wouldn't work for script hosts. The host needs to be able to pass customized arguments to the script.
script.csx: Print(Args);
Print(Environment.CommandLine);
It's true that C# proper could behave differently, but then it might get confusing. |
|
@Grauenwolf Sure, but if you make the content different from |
Well I feel foolish, we already have You just got to know which items to ignore from the array. |
Hi all, @MadsTorgersen I have read the proposal https://github.com/dotnet/csharplang/blob/master/proposals/top-level-statements.md and I have one suggestion: Do not allow user to use top-level return statement, because it is very confusing for programmer !! await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0; This example confusing ... await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!"); the following: static class $Program
{
static async Task<int> $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0; // This line added implicitly
}
} If user want to exit from program in top-level statement: await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
Environment.Exit(1); will yield the following code: static class $Program
{
static async Task<int> $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
Environment.Exit(1);
return 0; // This line added implicitly
}
} or after optimization: static class $Program
{
static async Task<int> $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 1; // This line added instead of Environment.Exit(1) statement
}
} |
This looks awesome from a teaching perspective, since "public static void Main" currently has to be treated like a magic incantation for programs by beginners. Why not allow class definitions between top-level statements, though? |
It doesn't mean it is an irregularity. It means top level statements are exactly the same as if you put the same code inside a public static void Main. You could have a simple refactor that moved it inside. |
If top-level statements have to be explained in terms of what public static void Main is, that makes them a lot less useful. IMHO it should ideally be the opposite: first teach people about basic expressions in a REPL, then move to a file with top-level statements, teach them about adding classes to that file (anywhere!), then finally explain that for real projects it makes more sense to contain those statements in special method named Main (using the knowledge that classes exist) |
::::then finally explain that for real projects it makes more sense to contain those statements in special method named Main |
And then there's me, who asked to be able to define a class within a method a long time ago :D |
@MadsTorgersen using System;
Console.WriteLine("Hello World!"); and
using System;
Console.WriteLine("Hello World!"); Compiler should generate the following code: using System;
class $Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
} For using System;
class $SomeFile
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
} It should be possible to specify StartUpObject/StartUpFile:
|
The C# compiler currently understands a dialect of the language used for various scripting and interactive purposes. In this dialect, statements can be written at the top level (without enclosing member bodies) and non-virtual members (methods etc) can be written at the top level (without enclosing type declarations).
Usage of the scripting dialect has been relatively minor, and in some ways it hasn't kept up with the "proper" C# language. However, usage is picking up through Try.NET and other technologies, and I am concerned with being stuck with two incompatible dialects of C#.
I think there is merit to allowing top-level program code instead of requiring the "program" to be in a
Main
method. And I think there is merit to allowing functions to be declared at the top level of a program, without needing to be enclosed in a class. I think these allow small programs to be meaningfully simpler, and make the language easier to learn.As opposed to:
I believe we should consider putting these extensions into C# "proper" and do away with the separate scripting dialect. In doing so we should not feel too bound by design details of the scripting dialect, but make sure we resolve those details in a way that's best for C# as a whole. I'm not so worried about "breaking" the C# scripting dialect. Since its use is mostly interactive, there probably isn't going to be all that much source code around that depends deeply on its semantics.
I hope that adding these features can help create a more continuous growth path from someone experimenting in a Jupyter workbook to having a full-blown C# application, and that sort of scenario.
I don't have a concrete fleshed-out proposal at this point. I think we should start by reviewing the current state of affairs and brainstorming what would work well and less well from the scripting dialect.
The text was updated successfully, but these errors were encountered: