123 lines
6.8 KiB
Markdown
123 lines
6.8 KiB
Markdown
|
|
# C# Language Design Meeting for Feb. 26, 2020
|
|
|
|
## Agenda
|
|
|
|
Design Review
|
|
|
|
## Discussion
|
|
|
|
Today is a design review, where we collect the design team, selected emeritus
|
|
members, and a number of broad ecosystem experts to provide some "in-the-moment"
|
|
feedback to our current designs and their directions.
|
|
|
|
Today we presented more of our thoughts on the top-level statements/"simple programs"
|
|
features and records.
|
|
|
|
### Simple Programs
|
|
|
|
We have a prototype of simple programs. As per the existing spec, you can have
|
|
top-level functions among all files, and other statements in a single file.
|
|
|
|
Collected feedback, not in any particular order:
|
|
|
|
* Supporting local functions in files other than the top-level statement file doesn't
|
|
seem useful and could cause confusion. If there's a local function defined in a file
|
|
only with classes, it would appear that that function would be in scope for the
|
|
classes, according to C# lexical scoping conventions. However, since these are defined
|
|
as *local functions*, not top-level methods, it would be an error to use them inside
|
|
the class. Moreover, even if that confusion is resolved, there doesn't seem to be a
|
|
compelling reason to allow it in the first place. Because these functions are not
|
|
accessible from anything except the main statement file, it would be most likely to
|
|
want to put the functions in that file, next to the uses. The only benefit from allowing
|
|
local functions in separate file may be as a helper file that is linked into other
|
|
compilations. However, wrapping these utility functions in a class so they can be used
|
|
in more places seems like a small burden with big benefits. Once wrapped in a class,
|
|
these functions are simply methods like in C# today.
|
|
|
|
* Mixing classes and statements in the same file could generate some confusion. The existing
|
|
design is that classes can see the variables created by statements, but it would be illegal
|
|
to reference them. This keeps the option open to allow access later. However, this could be
|
|
a confusing middle ground. To simplify the situation we could require only statements in the
|
|
top-level in one file (forcing all types to be declared in separate files). However, there is
|
|
interest in using utility classes in the top-level statements, perhaps especially with a
|
|
forthcoming records feature that provides simple, short syntax for declaring new types.
|
|
|
|
* Many of these features mirror what we already have in CSX. It's good that our
|
|
current designs are similar and allow these constructs in more places, but since the semantics of
|
|
this design have subtle differences from CSX this would effectively create a third dialect of C#.
|
|
There's some desire to unify these worlds, but it's difficult. CSX is designed to allow all
|
|
values to be persisted, which is important for the scripting "submission" system, but this makes
|
|
a number of types of statements illegal that we have support for in the current design, like
|
|
ref locals. It also creates a burden for new designs, where statements need to be explicitly
|
|
designed for both CSX and C#. For example, the new `using var` declaration form is nonsensical
|
|
under the CSX design and probably should be illegal. Since the current 'simple programs' design
|
|
effectively treats statements like they are part of a method body, there's a cleaner semantic
|
|
parallel with C#, meaning less special-casing.
|
|
|
|
### Records
|
|
|
|
Here we presented a variety of different pieces of designs we have been thinking about.
|
|
|
|
#### Nominal Record
|
|
|
|
Feedback:
|
|
|
|
* One of the biggest drawbacks of the current writeable-property style in C#, where types are
|
|
declared with public mutable properties that are then initialized using object initializers,
|
|
is that author can't enforce that certain properties are always initialized. It would be a
|
|
big disappointment if any "nominal records" feature that we built couldn't support this feature.
|
|
|
|
* With the design as-is there's no way to validate the whole state of the object. However, that's
|
|
also true of the object-initializer style currently in use, and this doesn't seem to be as a big
|
|
of a problem for current users.
|
|
|
|
#### Value Equality
|
|
|
|
* Positive feelings on generating `.Equals(T)` and implementing `IEquatable<T>`, mixed feelings
|
|
on generating the `==` and `!=` operators.
|
|
|
|
* If we opt-in the whole class using `value class`, we also need an opt-out for individual members.
|
|
Regular classes also often have somewhat specialized equality requirements, like wanting to compare
|
|
certain lists as sequence-equal, or compare strings ignoring case. This observation points to a
|
|
lot more customization points for value equality on general classes than value equality on records.
|
|
|
|
* Using value equality on mutable state is seems dangerous if the type is used in a dictionary,
|
|
but despite the danger, other languages (Java, Go) have value equality for common types, like
|
|
lists, that can be easily added to a dictionary.
|
|
|
|
* We don't currently have a robust mental model for what it means to be a "value class." Is "value
|
|
class" a separable concept from "implementing value equality," which people often do today? Or
|
|
is it not a different type of class, but simply a modifier signaling an implementation detail,
|
|
namely that the compiler generates value equality automatically. If we think of value equality
|
|
as a public contract, how does that change our view of existing code? Classes can currently
|
|
override Equals, but we don't distinguish what *kind* of Equals they provide. That isn't a
|
|
language concept, in a sense, but a part of the documentation.
|
|
|
|
### Nominal Records
|
|
|
|
* When using the `with` expression on nominal records, the generated parameter-less `With` method
|
|
looks a lot like `Clone`. It does little aside from return a new object with a shallow copy of
|
|
the state. If `With` is essentially Clone, why not use one of the existing forms of Clone that we
|
|
already have?
|
|
|
|
* ICloneable is deprecated and MemberwiseClone is protected. Maybe we should just call the method
|
|
Clone(), but not override or implement any of the other framework uses.
|
|
|
|
* This feature looks a lot like structural typing from other languages, like Javascript's "spread"
|
|
operator, but that is not the feature we're currently trying to build. This is feature is still
|
|
about declaring new types, not providing some compatibility between existing types.
|
|
|
|
* We spent a lot of time talking about validation and "validators", a very recent concept that was
|
|
floated as an alternative to constructors, executing after the `with` expression.
|
|
|
|
* There's some general concern about having no capability of validation, but no consensus on
|
|
exactly how validators should work.
|
|
|
|
* If validators work against the copied fields of the object, that seems to imply that the
|
|
fields are the state being operated on. On the other hand, only certain members can be
|
|
mentioned in the `with` expression. Why wouldn't those be the things that are copied? Instead
|
|
of all the state?
|
|
|
|
|
|
|