csharplang/meetings/2020/LDM-2020-03-09.md
2020-03-24 00:33:54 -07:00

4.7 KiB

C# Language Design for March 9, 2020

Agenda

  1. Design review
  2. Records

Discussion

Simple Programs

In the design review we covered the "simple programs" feature. A big piece of feedback is that they didn't like the "middle ground" we had carved out with local functions. Right now we have local functions that can exist both in the top-level statement "file" and also across other files. Because the design allows only local functions at the top level, those functions are only accessible from and can only access the statements in the "top-level statement file."

The feedback was that this would be an unfortunate design point. Organizing local functions in other files, when they can't be accessed by other files, is a big problem. There are two places we could go with this. We could either pull back and allow many of these forms only in the "top-level statement file." Or we could go the other way and allow more functionality at the top level, like allowing truly top-level members, including functions and variables, that can be declared and referenced from everything in the program.

Allowing full top-level members is attractive but opens a lot of questions. The most important is top-level variables. By allowing top-level variables and giving them the C# default of mutability, we would effectively be enabling mutable global variables. There is collective agreement that in everything but the smallest programs this is a bad programming practice and we shouldn't encourage it.

We could try to pivot on syntactic differences. One major difference between local variables and functions and member-level variables and functions is that members allow accessibility modifiers. Top-level statements could be, by default, local variables. Accessibility modifiers, public, private, internal, et al., could differentiate between top-level and local variables. However, this feels like it may be too subtle a design point, relying too much on minor syntactic decisions to decide things like scoping.

The biggest takeaway is that this is a complex topic with a lot possibilities. Maybe we could try to carve out a small portion to make some progress, without committing to a narrow design for the entire space of "top-level members." We generally like this approach, but CSX makes things difficult. Since the existing design focuses on local variables and local functions, compatibility with any sort of submission system is a problem. Fundamentally, we would need to decide which pieces of state in a C# program are "transient," in that they cannot be referenced from a new submission, and which are "preserved."

The value still seems important enough to move forward. There's general agreement that top-level statements are useful. Some people think they are useful in the simple form already presented, while other people want to see this as the starting point for a full feature, and these views are roughly compatible.

If we move forward with our subset, we need to flesh out the mental model for how it works. Specifically, it's important to note why top-level variables and functions would be inaccessible from inside classes. One way to think about this is the difference between instance and static. When you're in a static context, all the instance variables are visible, but not accessible. You could also model it as the statements are directly inserted into Main (which is true).

Conclusion

We'd like to move forward with the prototype with the modification that top-level statements can only appear in one file. Symbols declared in these statements would be visible, but an error to access inside classes. All the top-level statements are treated as if they were inside an async Main method.

Value equality

When we discussed records in the design meeting we brought up value equality for records and a proposal for extending to regular classes. A big shift was that records should have an easy global automatic value equality, while general classes should never have a global opt-in.

This seems contradictory, but if records have a set of default semantics that naturally fit value equality, then having it enabled by default is suitable. Value equality plays particularly well with immutability. Since records strongly support immutable programming, supporting value equality is natural. For arbitrary classes, however, it's not clear at all how value equality should behave. Opt-ing in either all or only some members seems to have downsides for many class examples.

We're not ruling out value equality for regular classes, but for the future we'd like to examine specifically how we'd like value equality to work for records. This could impact how and when we bring generated value equality to conventional user classes.