dotnet / csharplang

The official repo for the design of the C# programming language
11.36k stars 1.02k forks source link

Top-level statements and functions #3117

Open MadsTorgersen opened 4 years ago

MadsTorgersen commented 4 years ago

Top level statements and functions

There are at least three somewhat conflicting scenarios around allowing statements and/or functions to be declared at the top level of program text.

First I'll consider each in turn, and point out how they conflict with each other. Then I'll propose an approach for C# to take.

Scenario 1: Simple programs

There's a certain amount of boilerplate surrounding even the simplest of programs, because of the need for an explicit Main method. This seems to get in the way of language learning and program clarity.

The simplest feature to address this would be to allow a sequence of statements to occur right before the namespace_member_declarations of a compilation_unit (i.e. source file).

The semantics are that if such a sequence of statements is present, the following type declaration would be emitted:

static class Program
{
    static async Task Main(string[] args)
    {
        // statements
    }
}

This would make it an error for multiple compilation units to have statements, because you'd get two classes with the same name Program. If the assembly is run, it would also be an error to have other valid entry points, such as explicit Main methods.

Scenario 2: Top-level functions

C# restricts named functions to being declared as members of types, as well as local functions. The closest you can get to a notion of "global" (or "namespace-global") functions is to put them as static members in a class C and then say using static C; in places where you want to use the functions directly without prefixing with a class name. This adds ceremony to both the declaring side and the consuming side dealing with the class C.

The simplest feature to address this is to add function declarations to namespace-member_declarations - the kind of thing you can declare globally or in a namespace.

The functions would be limited in the modifiers that apply: They cannot be abstract, virtual, override or sealed. Their accessibility, like that of top-level classes would be internal or public, with internal being the default.

There's a design decision as to which kinds of function member declarations would be allowed: methods are key, but properties, indexers, etc. could also be considered. You could even consider stateful members (fields, auto-properties), and you would essentially get global variables. User defined operators and conversions are probably completely off the table, though, as they have relationships with their enclosing type, and there wouldn't be one.

On the consuming side, the top-level members would be direct members of the namespace, just as top level types are. If the namespace is usinged, or is the global namespace, the members are directly in scope.

The implementation would be that a partial class is generated to wrap the members as static members. The class name would probably be unspeakable, and would be chosen in such a way that the same name wouldn't be used twice in the same namespace across different assemblies. If any of the top-level members are public, the class would be public, and marked in such a way that a consuming assembly would know to expose the members directly.

Scenario 3: Scripting

There is currently a "scripting dialect" of C#, where top-level statements and functions are not only allowed, but are the way the program is specified. It's similar to scenario 1, except that the statements are freely scattered among type declarations. (Namespace declarations are currently not allowed in scripting, but that may change in the future.)

The execution of a script is often performed by a "host", that is able to put specific things into scope of the script, as well as access the state of its "local" variables. This is enabled by the state being represented as instance fields of a generated class, of which the running script is an instance.

Also, scripts can be executed as individual "submissions", one after the other, with subsequent ones being within the scope of their predecessors' declarations, modulo shadowing. In this mode submissions need to be captured as objects, and cannot allow stack-only things such as ref variables. Similarly, scripts are implicitly async so that await can be used freely, and this also limits the use of certain features.

If we want to add top level statements and functions to C#, we don't want them to conflict with how those work in scripting. Rather we want to compile them in the requisite manner when necessary, but unify on the semantics of the features. This doesn't fully eliminate the scripting dialect, as we would still need to deal with the special directives and "magic commands" that it requires, but at the minimum we do need to avoid the same syntax to not mean materially different things.

Problem

The main conflict between these three scenarios is in how top-level functions are construed. Are they "local-to-the-main-program" functions (as in 1 and 3), or are they top level library declarations just like types (as in 2)?

If the former, then top-level functions can only occur as part of a top-level program. They can see the local variables of that program, but they (and the local variables themselves) aren't visible to e.g. adjacent type declarations.

If the latter, then top-level functions can occur everywhere top-level type declarations can occur. They wouldn't be able to access the state of a top-level program, unless we also interpret the "locals" of such a program as top-level "global" variables. The functions - as well as such global variables if we choose to embrace them - would be members of their namespace, visible to any code in the assembly, and, if declared public, to any other assemblies referencing it.

Proposal: Simple programs

You can squint and imagine a merged feature that serves all the scenarios. It would require a lot of design work, and some corners cut. I do not propose that. Instead I suggest that we fully embrace scenario 1, essentially fleshing out and slightly generalizing the feature sketched out for that scenario above.

The primary goal of the feature therefore is to allow C# programs without unnecessary boilerplate around them, for the sake of learners and the clarity of code. A secondary but important goal is to not introduce a fundamental conflict with scenarios 2 (which we may want to revisit in the future) and 3 (not having the meaning of top-level code differ between "program" and "script" scenarios).

It should be relatively straightforward to ensure that, while more restrictive than scenario 3, for programs that are allowed, the semantics will be approximately the same; enough so that the two don't materially conflict.

The approach more fundamentally clashes with scenario 2, and in its straightforward form it would bar us from extending the feature to embrace scenario 2 in the future. I propose that we build in additional restrictions to keep that design space open.

(If we later find that there's a need for libraries of top-level functions, we can also consider an equivalent to VB's modules, which still provide a named wrapper for static members (similar to a static class), but put the names of those members in scope implicitly when the enclosing namespace is usinged, instead of requiring an explicit using static).

Syntax

The only additional syntax is allowing a sequence of statements in a compilation unit, just before the namespace_member_declarations:

compilation_unit
    : extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
    ;

In all but one compilation_unit the statements must all be local function declarations.

Example:

// File 1 - any statements
if (args.Length == 0
    || !int.TryParse(args[0], out int n)
    || n < 0) return;
Console.WriteLine(Fib(n).curr);

// File 2 - only local functions
(int curr, int prev) Fib(int i)
{
    if (i == 0) return (1, 0);
    var (curr, prev) = Fib(i - 1);
    return (curr + prev, curr);
}

Note the use of return as a top-level statement. We may find that this looks/feels wrong since it's not visibly inside a body of a member.

Semantics

If any top-level statements are present in any compilation unit of the program, the meaning is as if they were combined in the block body of a Main method of a Program class in the global namespace, as follows:

static class Program
{
    static async Task Main(string[] args)
    {
        // File 1 statements
        // File 2 local functions
        // ...
    }
}

If any one compilation unit has statements other than local function declarations, those statements occur first. The order of statement contributions (which would all be local functions) from other compilation units is undefined.

Warnings about missing await expressions are omitted.

Normally collision between multiple Main method entry points is only diagnosed if and when the program is run. However, we should consider forbidding any Main methods suitable as entry points to coexist with top-level statements. Or if we do allow them, we should not allow synchronous ones to silently take precedence over the async one generated from the top-level statements. That precedence was only reluctantly allowed over async Main methods for back compat reasons which do not apply here.

The example above would yield the following Main method declaration:

static class Program
{
    static async Task Main(string[] args)
    {
        // Statements from File 1
        if (args.Length == 0
            || !int.TryParse(args[0], out int n)
            || n < 0) return;
        Console.WriteLine(Fib(n).curr);

        // Local functions from File 2
        (int curr, int prev) Fib(int i)
        {
            if (i == 0) return (1, 0);
            var (curr, prev) = Fib(i - 1);
            return (curr + prev, curr);
        }
    }
}

Scope of top-level parameters, local variables and local functions

Even though the args parameter and top-level local variables and functions are "wrapped" into the generated Main method, they should still be in scope throughout the program, as if they were declared with internal accessibility in the global namespace.

This could lead to name collisions, ambiguous lookups and shadowing of imported names. If one is picked by name look-up, it should lead to an error instead of being silently bypassed.

In this way we protect our future ability to better address scenario 2, and are able to give useful diagnostics to users who mistakenly believe them to be supported.

LDM notes:

MadsTorgersen commented 4 years ago

See relevant discussion at #2765.

CyrusNajmabadi commented 4 years ago

The primary thing that bothers me here is scoping. i.e. both around 'args' as well as the scopes of variables introduced in teh statements that precede a namespace. However, it may just be an initial aversion that i coudl get over.

Given that your goal is Proposal: Simple programs, i would say that there's actually no need for scoping to extend from teh statements elsewhere. A simple program is just statements and little funcs. The moment we get to namespaces/classes/etc, we're no longer "simple" and i personally would prefer stating that they shouldn't mix.

--

another issue for me is that while this is pitched as 'simple programs' it seems to still allow the statements to coexist with namespaces/classes. First, this isn't really 'simple' to me anymore. Second, it actually opens up large cans of worms for me. For example, if i were to be able to have top-level statements that can be in scope for the rest of my program, then I absolutely would want to be able to make those top level variables readonly so they couldn't just be overwritten by the rest of my code.

I strongly like the idea of simple-programs. But I actually don't think this goes far enough. Perhaps a simple program should only be top level statements/local-funcs?

HaloFour commented 4 years ago

Where do imported namespaces come in? Or do we need support for using directive within a method in order to support this? Or would the compiler take using directives and "promote" them outside of the generated class? What if multiple files want to import multiple and potentially colliding namespaces?

It's difficult to not have an immediate negative visceral reaction to this proposal. It feels like it creates yet another dialect of the language without solving for any problems or the use cases suggested. You wouldn't be able to take CSX and run it this way, not without additional syntax work. You couldn't use most of the language as you'd expect. All variables end up in some mixed global scope.

Feels like tools like LINQPad already satisfy this need and do so in a vastly superior manner.

orthoxerox commented 4 years ago

@CyrusNajmabadi I agree that simple programs should remain simple. I am not even convinced that splitting your simple program into multiple simple files is something desirable. I imagine that csc run hello.cs or dotnet run hello.cs would be the preferred mode of running them. As soon as you have multiple files you need either a csproj or some other way to refer to multiple files. The former is complicated enough that you might as well rote learn static void Main as well, while the latter leads learners away from the "proper" way of doing things.

Of course, the scripting dialect could invent its own way of including other files, but that's outside the scope of this issue.

amdav commented 4 years ago

I agree with @MadsTorgersen proposal, that this feature should be targeting simple programs and people learning the language (and should be a shortcut for what goes inside Main()). It could be worth adding a little more detail (with examples) to the section on the scoping of local variables and functions just so everyone is clear.

YairHalberstadt commented 4 years ago

I'm feeling a little bit sceptical of this proposal.

Writing a static Main function is not particularly difficult, and the tooling generates it for you anyway. In terms of benefits of top level statements I would suggest there's almost none from an actual use case perspective.

Instead I feel this is more of a marketing issue. C# looks old and stuffy because you need so many things to create an app. Python you just type something and it runs.

Marketing is important, but I don't think it's worth introducing a whole load of complexity for it. Instead I would keep this extremely simple. You can have a single file in a project with top level statements, which act exactly as if they're inside an async Main method. They are not globally scoped, and can't be referenced anywhere else. That should be enough to give beginners their python feel.

fitdev commented 4 years ago

For my part I really like Scenario 2. That would allow for significant time savings via less typing. And having worked with VB.NET for a number of years, I do miss VB's modules and their implicit imports.

User defined operators and conversions are probably completely off the table, though, as they have relationships with their enclosing type, and there wouldn't be one.

I would love to have globally accessible implicit and other operators I could define even for existing CLR types. So if the team can find a way to include those it would add even more value to Scenario 2.

orthoxerox commented 4 years ago

@YairHalberstadt I would absolutely use simple C# programs for throwaway scripts and tiny utilities. dotnet new console is fast, but it leaves behind a whole project folder with a bunch of binaries.

YairHalberstadt commented 4 years ago

@orthoxerox Would you require more than one file?

orthoxerox commented 4 years ago

@YairHalberstadt one file per utility/script. I've already written I don't think it's nesessary to support multi-file "simple programs".

HaloFour commented 4 years ago

If all this proposal does is take the statements in the file(s) and put that into the middle of a generated Main method why does this need to be a part of the compiler or C# spec at all? Couldn't a separate dotnet tool be shipped which does that for users? A simplified tool could be geared towards making this as easy as possible. And you could prototype this all out right now without having to touch Roslyn or the C# spec.

orthoxerox commented 4 years ago

@HaloFour It's a bit more complex than that, since the tool has to recognize the usings as well.

HaloFour commented 4 years ago

@orthoxerox

It's a bit more complex than that, since the tool has to recognize the usings as well.

I see nothing in this proposal that claims that using directives would be supported. That would land us firmly back into dialect territory since said directives aren't permitted mid-method. But, if this proposal would need to support it, without a corresponding change to the language, then an external tool could also do so just as easily. CSX already does it.

IMO, we'd get more mileage making it easier to get/use CSX, and adding support for converting CSX scripts to a C# project.

svick commented 4 years ago

For me, the simple programs I would want to write:

This means that for me, the suggested ordering of "statements right before the namespace_member_declarations" would not be natural, I'd prefer it the other way around.

And being able to have top-level local functions in other files and being able to access top-level local variables from other files is unnecessary and probably undesirable.

On the other hand, the suggestion by @CyrusNajmabadi of having only "top level statements/local-funcs" goes too far: being able to declare types is necessary for the "simple programs" I want to write.

CyrusNajmabadi commented 4 years ago

Very interesting point @svick . Thanks!

There's something definitely more appealing to me about that as it feels much more 'natural' in terms of scoping. i.e if i think of the 'top level locals' i declare as similar to 'method locals', then placing them after everyting else feels 'better' (since locals can't be referenced by code that is earlier than their declaration).

orthoxerox commented 4 years ago

@HaloFour Take a look at the grammarlet Mads posted:

compilation_unit
    : extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
    ;

Usings are supported, and they must precede the statements. I actually don't mind if CSX is merged with mainline C#, I can then just treat this proposal as the first step in that direction.

Thaina commented 4 years ago

Would like to voice that I prefer the scenario 2 specifically

Is it true that I could assume that top level function will be internal static by default? If it is then, as for simple program purpose, just one void Main is already reduced enough from the verbose class and static

Another main reason I really support this feature is that, outermost void Main will become exactly one in the whole program, never be any conflict thereafter. It also true for any same name singleton function we want

Also I would like to repeat myself that, I wish that we could not write top level statement. Only function and property. Because, unlike class that easily make dependency chain, we cannot expect timing and order of the statement aside from void Main

DavidArno commented 4 years ago

Scenario 2 is also my preferred option.

But I disagree with @MadsTorgersen suggestion of:

Their accessibility, like that of top-level classes would be internal or public, with internal being the default.

private should be supported and should be the default, in my view. Each file containing its own set of free functions should end up as a differently named static class, allowing each to contain its own private functions.

YairHalberstadt commented 4 years ago

private should be supported and should be the default, in my view. Each file containing its own set of free functions should end up as a differently named static class, allowing each to contain its own private functions.

I think that would be very useful to support an FP rather than class based style of programming. Each file becomes a sort of module and defines a number of private, internal, and public functions.

MgSam commented 4 years ago

I don't understand the use case here. Is something wrong with 'csx' scripts? Why can't 'learners' and those who want 'simple programs' just use the already-existing scripting dialect?

If the reason is "no one is using it"- that's a tooling and education problem, not a language design problem. You guys released CSX and then did close to zero promotion of it- to my knowledge, no one official has ever blogged about it or demoed it at conferences, and development on C# Interactive stopped almost as soon as it started.

genlu commented 4 years ago

I strongly like the idea of simple-programs. But I actually don't think this goes far enough. Perhaps a simple program should only be top level statements/local-funcs?

@CyrusNajmabadi I agree. IMO, having both top level statements/local-funcs and "regular" C# code coexist as proposed here can work, but allowing them to mix might be against the simple program scenario that motivated this proposal in the first place.

However, this would pretty much make it identical to a csx, right? It seems to me that making improvement to existing C# scripting would address the simple program scenario more effectively.

iam3yal commented 4 years ago

@MgSam The motivation here is to come up with a single dialect where you wouldn't have to think about the environment regardless to proficiency or task, people who just want/need to write a "simple program" and run it shouldn't use a different tool just because now they want to define a top-level function or whatever but for beginners this might be a show stopper especially for new programmers with no prior knowledge and this barrier is a language concern because there are two different dialects and two different tools for the same language.

iam3yal commented 4 years ago

@AlgorithmsAreCool With all due respect your comment makes zero sense here.

AlgorithmsAreCool commented 4 years ago

@eyalsk In retrospect, I agree. I've removed it.

HaloFour commented 4 years ago

@RUSshy

Does it really solve that problem, though? Sure, you might get "Hello World" off the ground faster, but for anything even slightly less trivial you're going to have to jump that same hurdle.

And does that hurdle really exist, even for beginners? Odds are that a beginner is starting from a tool like Visual Studio or dotnet new which writes all of that boilerplate for you. If they're trying to write this program from the command line they've already had to vault significantly higher hurdles.

If adoption and outreach are the goals I'd suggest that there are significantly better opportunities that don't result in creating dialects of the language. And if the Tiobe index is to be trusted it doesn't appear that C# is having too many issues attracting developers. Neither do most of the other languages towards the top of that list most of which require some kind of syntactic boilerplate.

Oh, and bring back temporary solutions in Visual Studio. The removal of that feature has been infinitely more annoying to my ability to toss together a quick&dirty project than having Visual Studio automatically generate some code around the code I want to write.

Thaina commented 4 years ago

@HaloFour

dotnet new which writes all of that boilerplate for you

In the eye of beginner programmer, even the word namespace and class itself is already magic that require them to understand that they cannot put a logic code inside those block. They need to learn that they could only put code into the bracket of void Main

Starting with dotnet new will present them a sudden 3 layers that require understanding. This proposal can reduce to only one (or zero, if we could write a top level statement)

HaloFour commented 4 years ago

@Thaina

In the eye of beginner programmer, even the word namespace and class itself is already magic that require them to understand that they cannot put a logic code inside those block. They need to learn that they could only put code into the bracket of void Main

I disagree that such syntax poses a burden to beginners, either to C# or to programming in general. They're going to have to learn so much about the syntax of C# in order to put anything anywhere anyway, and many of those concepts (like variables, definite assignment, etc.) are so much more complicated to grasp than requiring a single container around a method (namespace has always been optional). What's next? Implicit variable declaration by assignment? Auto-importing namespaces? Automatically emitting the output of any expression to the console?

If C# is going to seriously consider the addition of top-level functions it should do so in consideration as to how they would impact and benefit developers of all skill levels and projects of all shapes.

Thaina commented 4 years ago

@Thaina

In the eye of beginner programmer, even the word namespace and class itself is already magic that require them to understand that they cannot put a logic code inside those block. They need to learn that they could only put code into the bracket of void Main

I disagree that such syntax poses a burden to beginners,

This might be only my opinion. But I still remember when I myself was a beginner. So this is my direct experience. I have learn only a little bit of C and start learning Java and C# at the same time. I remember I feel much burden on the package/namespace and class that was unknown to me. I don't know what it is and what effect it has on my code. What can I change in that file. What is the meaning of it. Where could I start typing. Looking back now I still felt that burden, partly because I always want this feature so it always reminded me on that first day

It very easy when you have learn C or older language as a starting point to programming. You already know what a code of program really is. It really another story when you don't really know it but just start learning C# or Java as your first ever language in your life. And I think most of us here cannot experience that kind of experience anymore

I have watch one 3Blue1Brown chapter, he talk about "This problem seems hard, then it doesn't, but it really is". He make that video about question in math competition that the organizer think it is quite easy, but the participant cannot solve it The conclusive word he give is, "It extremely hard to imagine what it feel like to not understand". And I kind of thinking the same with our understanding of programming language. What we feel like easy is easy from our perspective with some experience. We then sometimes fail to understand what it feel like to not understand, because we can't imagine it anymore

Thaina commented 4 years ago

If C# is going to seriously consider the addition of top-level functions it should do so in consideration as to how they would impact and benefit developers of all skill levels and projects of all shapes.

As for me I think this feature already benefit us if we could write only one void Main in the project. Made second and it will give a name collision error. It pin down that we would have only one entrypoint in the project ever. That was the main benefit for all singleton function

My point is, the benefit for beginner that some people talk about is also as real and also a great bonus for our language too

ErikSchierboom commented 4 years ago

In the eye of beginner programmer, even the word namespace and class itself is already magic that require them to understand that they cannot put a logic code inside those block. They need to learn that they could only put code into the bracket of void Main

I agree with this. There is a rather significant amount a syntax a beginner would have to read and understand to know what is happening. The less this is happening, the best.

ghost commented 4 years ago

I also want to voice my support for the opinion that the ceremony with classes, namespaces and especially void Main is a real obstacle for the beginners. I myself chose Pascal as a first language to learn for this literal reason. Good thing it was Pascal.NET, so I had to learn C# anyway to understand documentation.

iam3yal commented 4 years ago

If C# is going to seriously consider the addition of top-level functions it should do so in consideration as to how they would impact and benefit developers of all skill levels and projects of all shapes.

This is purely subjective and a slippery slope because this doesn't apply to many of the other features introduced to C#.

HaloFour commented 4 years ago

@eyalsk

This is purely subjective and a slippery slope because this doesn't apply to many of the other features introduced to C#.

I disagree. It's been my experience that virtually every feature that has been adopted by the C# team and added into the language has had to meet this same subjective but high bar in that it must either benefit sufficient developers directly or must benefit runtime/library authors sufficiently that other developers feel that benefit.

iam3yal commented 4 years ago

@HaloFour I agree with what you said and I don't think it's remotely close to what you said previously where you state the following:

If C# is going to seriously consider the addition of top-level functions it should do so in consideration as to how they would impact and benefit developers of all skill levels and projects of all shapes.

Which I disagree with but anyway, maintaining two different dialects and two different tools of the same language is that high bar imo but we'll see, maybe not.

HaloFour commented 4 years ago

@eyalsk

I agree with what you said and I don't think it's remotely close to what you said previously where you state the following:

Hm, I was trying to say the same thing both times. Language is hard. :/

Which I disagree with but anyway, maintaining two different dialects and two different tools of the same language is that high bar imo but we'll see, maybe not.

I don't think this changes that at all. We're not talking about making CSX a part of the language, we're talking about making a third dialect where the compiler pretends that everything you're writing happens to take place within Main, with all of the wonky limitations that would come with that. CSX remains a separate incompatible dialect, for a multitude of reasons. And due to the limited nature of this "simple" dialect you can't adopt it in any existing project, nor does it make sense (to me) for anyone to use it after they're done writing their first artifically simple "HelloWorld" program and need anything slightly more complicated.

That's what I mean about the broader appeal. "Simple programs" seem to target a very small subset of users who are either first starting out and don't understand the ceremony around their code (but somehow understand all of the complicated nuance required to actually write that code), or are writing the simplest of programs that can manage to be crammed into a single method. In either case my opinion is that the work necessary to even get that far is a much bigger hurdle in that you still have to setup a project or invoke the compiler manually, and in the former the tooling can autogenerate all of the ceremony for you which, IMO, makes its removal a moot issue.

My opinion is that for both newbies and experienced devs alike the ability to quickly prototype new code is served not by a compiler dialect but through an excellent REPL and sandbox environment. IMO it's not necessary for the compiler to officially support that dialect. Both JShell for Java and Ammonite for Scala support writing top-level statements directly into the REPL and neither language supports such a construct, and my experience with Scala/Ammonite is that this is not an issue for either newbies or experienced developers. The most important thing is the ability to very quickly spin up an environment into which you can bang out a series of statements interactively while following the state during the flow, not being limited to a very small subset of the overall syntax.

Note that the shortest "Hello World" in CSX is literally "Hello World". Same is true with JShell and Ammonite. If you consider that to be cheating the second shortest is WriteLine("Hello World"), with no using directives or even a semicolon. It's normal for a REPL to optimize around the common interactive use cases, which I think aligns well with the goal of outreach to new developers as well as quick prototyping for experienced developers.

Anyway, I think (we can all agree that) I've ranted enough about the subject. 😄

Thaina commented 4 years ago

@HaloFour From the scenario detail in this proposal I think we have 2 options we need to choose

Personally I prefer the second case, and I don't like the first case, I too think it is unnecessary. And also I wish it would have more constraint than csx, such that it would be better seamless with our current C#

But in both case it drastically reduced from current C# starting point

The point of this is not just for quickly prototype (still it is a bonus too) but

1 - The real newbie, actually really totally zero experience, will see this as a starting point to learn C#. Less verbose. Less token in the eye that could be distract them. And easier to just write something before or after

2 - Experience developer, which is us all, could really start any size project from this point. Unlike prototyping or experimenting in csx, we can really start project with this first file even without dotnet new. We could make this file in vscode, type it manually, save and press debug, without any need to open terminal and run command, we can have a service (such as omnisharp) detect one cs file in folder and start it compiler

This is just the benefit that this feature allow us to start the project with this file

Carl-Hugo commented 4 years ago

If this ever becomes more than a proposition, for what its worth, I think it would be essential to limit access to that top-level code and disallow sharing it with non-top-level code. The idea to limit top-level code to a single file seems like a good start, but it may be too limiting from a learning perspective.

To have taught introduction to programming several times to multiple people having a different level of knowledge/affinity with programming, I can state that simple concepts, like writing code between { and } can be tough to understand to some. So I think that the idea of making entry into the C# world more accessible is a good idea.

However, I believe that transforming C# into a scripting-like language is a bad idea. In conclusion, from my experience, globals are your enemy when designing programs even if they first appear as friends; they almost always end up causing more troubles that good 😉

HaloFour commented 4 years ago

@Thaina

That I believe is the idea "scenario 2" above, and I can get behind that idea. It's similar to Kotlin in that the top-level functions in a file are "promoted" to static methods in an autogenerated partial class. The compiler would then automatically bring those members into scope anytime you import the namespace in which they are defined, as if you did a using static on that autogenerated class. You can use this for single file "script"-like programs, or you can mix it into your existing projects. You still need a Main entry point, but you don't need a class or namespace around it.

For newbies and prototyping alike, I want this in Visual Studio:

Swift in xcode Playground: image

combined with features to show the flow of execution from SharpLab like this:

image

gulshan commented 4 years ago

Some opinions-

For C# proper, I would like to have scenario 2 "Top-level functions".

aka-nse commented 4 years ago

I like senario 2 but I view top level statement with skepticism.

"Only one entry point" rule is essential for most languages and it is also going on C#. I think it is a first content to learn for beginners, and explicit void Main() is better textbook than veiling with top level statement.

Thaina commented 4 years ago

Actually, after some thought, I think I don't against top level statement on its own. I am perfectly fine to have it in our language

But what I am against is, it will be ambiguous when there was more than one file contain top level statement in the same level

And so, if we have any error when there was a top level statement then I think I am fine with it

suppose

// A.cs
System.Console.WriteLine("A");

// B.cs
System.Console.WriteLine("B"); // Error, ambiguous entrypoint ?

// C.cs
using System;
namespace X
{
    Console.WriteLine("C"); // Top level statement in namespace?
}

// D.cs
using System;
namespace X
{
    Console.WriteLine("D"); // Error, collision with C.cs
}
tverweij commented 4 years ago

It would have been nice if @MadsTorgersen would have given the credits to the one that came up with this Idea, namely the former Visual Basic PM @AnthonyDGreen, see https://anthonydgreen.net/2019/04/23/top-level-code-scenario-a/ https://anthonydgreen.net/2019/04/24/top-level-code-scenario-b/ https://anthonydgreen.net/2019/04/30/top-level-code-scenario-c/ https://anthonydgreen.net/2019/05/03/top-level-code-scenario-d/ https://anthonydgreen.net/2019/05/23/top-level-code-scenario-e/

iam3yal commented 4 years ago

@tverweij Aren't they colleagues? I'm sure they speak or spoke about it.

munael commented 4 years ago

Dunno about top-level statements. Those can be a special case for running a file through a REPL or something like that.

Top-level functions are orthogonal and IMHO worth it just for breaking the illusion of static methods being somehow "truer" to OOP.

ghost commented 4 years ago

Top-level functions are orthogonal and IMHO worth it just for breaking the illusion of static methods being somehow "truer" to OOP.

I wonder if people will start making Solution.Project.Utils namespaces to save on typing usings. Is it even a bad thing if this is already achievable with using static? Feels like the only thing separating us from Solution.Project.Utils is the "one class per file" "rule". It seems like top-level functions might introduce a lot of confusion in terms of how to organize them in files/namespaces properly.

gafter commented 4 years ago

It is not clear to me if this championed feature is supposed to represent the same thing as #2765.

gafter commented 4 years ago

This proposal conflicts with the possibility of having top-level methods in C#. I'm not sure that is intended, but I think that would be a problem.

HaloFour commented 4 years ago

@gafter

This proposal conflicts with the possibility of having top-level methods in C#.

Would that be because any such top-level functions would have to be evaluated as local functions within this "Main" method? Or is there another reason?

gafter commented 4 years ago

Yes, that is the main reason.

julealgon commented 4 years ago

I'm sure this has been discussed at some point, but would it be completely unfeasible to have first-class support for top level functions in C# (i.e. functions that actually exist outside a class/struct and are visible from an Assembly instance via reflection for example, without being inside a Type?

I'm seeing that all proposals here suggest moving the elements inside a generated class, but this seems like a workaround solution to a bigger problem to me.

I've seen multiple people that hate object oriented programming that would appreciate being able to create fully procedural programs in C#. I imagine that could help market the language to a wider audience as well.