
# 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?



