Skip to content

Record as a modified type vs. a new type #1495

@RexJaeschke

Description

@RexJaeschke

This Issue pertains to (Draft) PR #1458.

Back in early 2022, when I first read the MS proposal for V9's records, and the corresponding MS on-line tutorials, I thought of records as a new type, and started spec'ing it that way. However, after the email thread below, I changed to making them a variation on existing types (classes and, in V10, structs).

================================================================

From: Rex Jaeschke
Sent: Sunday, March 13, 2022 12:00 PM
To: Mads Torgersen; Bill Wagner
Subject: BIG PICTURE decisions regarding the adding of records to the C# spec

Hi there, guys!

I’m making great progress with spec’ing V9 features, and am about 95% done with records, based on this proposal. So far, so good!

However, only a few days ago, I was made aware that in V10, what V9 calls “records,” are referred to as “record classes,” and support for “record structs” is added. (I also see active proposals for related stuff: non-record-primary-constructors, which might supersede primary-constructors.)

After studying the V9 proposal, I figured there were two ways I could add support for records as class-like types:

  1. Enhance the existing classes chapter in a non-trivial way, or
  2. Create a new “Record types” chapter

Make no mistake, either way, the classes chapter is impacted. (For example, that chapter already has wording about structs even though they are spec’d in their own chapter!)

There are pros and cons to both approaches.

I decided that the cleanest approach was #2, “Create a new ‘Record types’ chapter.“

However, now that I know about record structs in V10, I’m having second thoughts.

Using the approach I chose, for V10 that will involve renaming the records chapter in V9 from “Record types“ to “Record class types,” and adding another chapter, “Record struct types,” and adding many of the same qualifiers/caveats elsewhere. This is certainly doable and follows my current pattern.

The obvious, alternate approach is to cover V9’s records (V10’s record classes) inside the existing classes chapter, and then in V10, cover record structs inside the existing struct chapter.

I figure that the amount of work for both approaches is about the same. But which approach looks cleanest and friendliest for the (long-time) spec reader?

Now although it crossed my mind that, “Wouldn’t it be simpler if we combined these V9 and V10 features in the same spec?” I’m not advocating that at this time. Besides, it’s not clear to me that that would help much. We can certainly keep the V9 and V10 specs separate and chose an organization for V9 into which the V10 stuff can easily be accommodated. (We could even refer to records in V9 as “record classes!”

Although I’ve nearly completed going down choice #1, off the top of my head, if we choose to go with choice #2 instead, almost all of what I’ve done can be salvaged; it would just need to be massaged to fit in a different chapter/section/paragraph.

To see the impact of the V9 feature on the current standard, you might find it instructive to look at the attached (26-page) spec-instructions document that I’ve written so far. NOT INCLUDED HERE

As I see it, a major challenge here is that while a record class is a class, there are differences. Likewise, for a record struct being a struct. Sometimes we want to talk about classes and structs, sometimes record classes and record structs, sometimes about the reference types class and record class, or the value types struct and record struct. As such, many existing sentences of the form “class [and | or] struct …” will have to be changed to accommodate record classes and structs, whether they are in the same or new chapters.

Let me know what you think? In the meantime, I’ll keep working on the spec, going in the current direction.

BTW, whatever we decide here, as the V10 record struct proposal (not surprisingly) looks very much like the V9 record (class) proposal, once I get down with the V9 spec for records, I may as well do the V10 record struct spec as well, while I’m “in the groove!” to be consistent and not risk losing our thinking.

Rex

================================================================

From: Mads Torgersen
Sent: Monday, March 14, 2022 2:34 PM
To: rex ; Bill Wagner
Subject: RE: BIG PICTURE decisions regarding the adding of records to the C# spec

This is a great question! I’m a bit conflicted about this, but here are some thoughts.

I don’t think of records as a new “kind of type”. The v9 syntax certainly suggests that, and then v10 pulls back on it by saying record is really a modifier on class and struct, and standalone record is just a shorthand for record class. I think it’s important to communicate clearly that record classes are classes and record structs are structs, and they just have additional behavior on top of what follows from that.

Today the spec has a different chapter only for different kinds of types. By that logic there shouldn’t be a separate “Records” chapter, but instead the “Classes” chapter should describe what the record modifier does to classes, and the struct chapter should specify what it does to structs. Like today, the struct part can refer extensively to the class part where those are similar.

I do have sympathy for an alternative organization where there is a “Records” chapter. If so, I would have a single one covering both record classes and record structs, and I would place it after the “Structs” chapter. If I were writing a book and trying to make sure chapters had more even and balanced “weight” that would probably be how I would go. However, the spec as it is today doesn’t shy away from having whole deep concepts described nested within another chapter, and we do kind of have precedence for doing that a lot withing the “Classes” chapter, being the first chapter to describe a kind of type, and then referring back to it from subsequent chapters. This goes e.g. for partial types and for generics.

All in all, then, my order of preference probably is this:

  1. Embed the treatment of records in the “Classes” and “Structs” chapters
  2. Have a single “Records” chapter after the “Structs” chapter, treating both class and struct records
  3. Have separate “Record Classes” and “Record Structs” chapters. I don’t think we should do this.

Thoughts?

Mads

================================================================

From: Rex Jaeschke
Sent: Tuesday, March 15, 2022 1:38 PM
To: Mads Torgersen; Bill Wagner
Subject: RE: BIG PICTURE decisions regarding the adding of records to the C# spec

Great feedback, Mads; thanks for the quick reply!

While my thinking on this is still evolving, I’m increasingly inclined to agree with you that putting record class stuff in the Classes chapter, and record struct stuff in the Structs chapter, is the preferred way to go.

The way I’m currently looking at this is to take representative pieces of text in the current spec and looking at how they would need to be modified for each scenario.

In an attempt to completely discredit the idea of having class, record class, struct, and record struct be separate types, let me use an illustration of how unwieldy that would become.

The first substantive mention of classes/structs is in 7.3 Declarations, where we have the following text, which I have marked up (MARKUP NOT SHOWN HERE) to show how it would look with both record classes and record structs as separate types:

There are several different types of declaration spaces, as described in the following.

  • Each non-partial class, record class, struct, record struct, or interface declaration creates a new declaration space. Each partial class, record class, struct, record struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program (xxx). Names are introduced into this declaration space through class_member_declarations, struct_member_declarations, interface_member_declarations, or type_parameters. Except for overloaded instance constructor declarations and static constructor declarations, a class, record class, or struct, or record struct cannot contain a member declaration with the same name as the class, record class, or struct, or record struct. A class, record class, struct, or record struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class, record class, or struct, or record struct permits the declaration of overloaded instance constructors and operators. For example, a class, record class, struct, record struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature (xxx). Note that base classes do not contribute to the declaration space of a class, base record classes do not contribute to the declaration space of a record class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class, record class, or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to hide the inherited member.

This is rather cumbersome!

Now let’s consider your preference #1: Embed the treatment of records in the “Classes” and “Structs” chapters:

The spec text above requires absolutely no changes, which seems ideal.

We do not currently define the terms “class” or “struct.” They simply correspond, one-to-one, with the syntax involving the tokens class and struct, respectively. However, with the addition of support for record classes and record structs, that is no longer the case. Let’s look at the first sentence from the spec text above:

  • Each non-partial class, struct, or interface declaration creates a new declaration space.

We can now read the use of “class” as meaning all class-like thingies, and “struct” as all struct-like thingies. In a review of my current detailed proposal, this new understanding of these terms will work without any need for change in many (perhaps most) situations, as they already will accommodate non-record classes.

But what if we need to distinguish between non-record classes and record classes?

In 14.2.4 Class base specification|14.2.4.1 General, we have the following:

A class declaration may include a class_base specification, which defines the direct base class of the class and the interfaces (xxx) directly implemented by the class.

class_base
  : ':' class_type
  | ':' interface_type_list
  | ':' class_type ',' interface_type_list
  ;

In this case, “class” cannot mean all class-like thingies, as record classes need to be handled differently, as follows:

A record class declaration may include a record_base specification, which identifies the direct base record class of the record class being declared, and the interfaces (xxx) directly implemented by the record class.

  : ':' class_type argument_list?
  | ':' interface_type_list
  | ':' class_type argument_list? ',' interface_type_list
  ;

And we’ll have to modify the existing text, to distinguish between the two flavors of class, using something like the following:

A non-record class declaration may include a class_base specification, which defines the direct base non-record class of the class and the interfaces (xxx) directly implemented by the non-record class.

So, I think the new terms “non-record class” and “record class” are clear distinguishers (as would be “non-record struct” and “record struct.”) Note that I discarded “ordinary class” as an alternative. [Interestingly, when the C++ committee started their first formal spec, they had to distinguish between C’s structs and C++ enhanced-by-OO structs, and they introduced the concept of “plain old data type/structure” for the former. There are still vestiges of this in their current spec going by the name POD.] Although the “non-record” prefix is a bit unwieldy, we’re going to have to have the “record” prefix anyway, so why not the negative form as well? It certainly says what it means, and it means what it says!

It would be useful to mention the existence and use of the 2 general vs. 4 specific terms perhaps at the start of the Classes chapter and the Structs chapter. Something like:

Classes chapter: C# supports two kinds|forms of class: non-record class and record class. [[Blah blah blah expand a bit here , if necessary.]] Uses of the term “class,” on its own, or as “base class,” “derived class,” and “partial class” includes both class kinds/forms. To distinguish between the two kinds/forms, we use “non-record class” and “record class” instead. Likewise for structs in the Structs chapter.

If that seems like the way to go, then how to find all the places where the new terms should be used? The obvious way is to look at every occurrence of “class” and “struct,” but that would be too laborious, and my guess is that 98+% of them won’t need changing. Better to make a list of the differences between the record and non-record flavors and then go review/change/augment the text describing only those affected sections.

It seems to me that there is one thing we should do for the V9 spec that will make transition to the V10 spec trivial, both for TG2 and the public readership: Although the V9 proposal only talks about “record” on its own, but on the understanding it’s a class-like thing, we should use “record class” instead, even though that term isn’t formerly introduced until the MS V10 proposal and has no corresponding explicit syntax in V9. It’s not wrong, it’s not misleading, and the only edit needed to accommodate the addition of the optional class after record come V10, is to add that to the grammar. No text changes would be needed. And the V10 record struct stuff would simply parallel the approach we take for V9. Also we should call the V9 grammar rule record_class_declaration instead of record_declaration, to match the V10 approach for record structs. Likewise for supporting grammar rules that begin with record_, such as record_body becoming record_class_body.

Eleven hours later and with fresh eyes, I’ve just gone over my current proposal (which has record classes as a separate type from classes), which impacts the following chapters:

6 Lexical structure
7 Basic concepts
8 Types
10 Conversions
11 Expressions
13 Namespaces
14 Classes
17 Interfaces
Annex C Standard library

However, if we make a record class a variation on class, the impact is considerably reduced, with only the following chapters impacted:

6 Lexical structure – make record a contextual keyword.
11 Expressions – differentiate between ==/!= semantics for non-record class vs. record class; add with expressions
14 Classes – add the record class stuff

Rex

================================================================

From: Mads Torgersen
Sent: Tuesday, March 15, 2022 5:21 PM
To: rex; Bill Wagner
Subject: RE: BIG PICTURE decisions regarding the adding of records to the C# spec

Hey Rex,

That’s some really good detail on this question! Here are my thoughts:

  • Let’s unify records and non-records more in the spec. Let’s not have a separate production for record classes (and structs) but merge it into the class (and struct) production by
    • Having an optional record modifier on classes and structs (and handling the odd case of record as a shorthand for record class in some way)
    • Having an optional parameter list in all class (and struct) declarations
    • Use prose to enforce the requirements around which combinations are allowed
    • Don’t have separate record_ productions as alternative to class ones ever. Instead have record_ productions only to describe elements that are only relevant to records class declarations that have the record modifier.
  • Treat record as much as possible like any other modifier: Describe the additional effects it will have locally where that modifier is explained. Don’t try to say that there are two kinds of classes. It’s just that some classes are (also) records. It’s a subtle nuance I know, but it’s true and I think it is helpful.
  • I like your idea to use “record class” over just “record” already in the v9 spec. This both helps with the above and sets it up better for v10.

I think what I describe ends up being even more surgical, and it jibes with the direction of the language. Many of the things that are record specific right now are considered to be generalized in the future: Primary constructors as you mention, the ability of records and non-records to inherit from each other, etc. So taking that approach already now helps avoid future reorganization as well as minimize current churn.

Make sense?

Mads

================================================================

From: Rex Jaeschke
Sent: Wednesday, March 16, 2022 12:14 PM
To: 'Mads Torgersen'; 'Bill Wagner'
Subject: RE: BIG PICTURE decisions regarding the adding of records to the C# spec

[Bill, see my comment at the end, which might affect your work in generating/maintaining on-line help/tutorial pages.]

I LOVE IT, Mads. As Strunk and White famously wrote, “Less is More!” Frankly, I was so excited to figure out how to do this, I was at my desk working on it at 4:30 this morning, and had it mostly figured out before breakfast!

To summarize, while the MS V9 proposal for record classes and the V10 proposal for record structs propose record classes/structs as almost-first-class concepts, we’re going to not do that. Instead, we’re going to push record-related stuff down into the weeds of (hopefully, only or at least mostly) the Classes and Structs chapters. Basically ,record is simply one of a number of options one can select when configuring the class/struct being declared.

The key to achieving this is in a modified grammar, a copy of which is attached. As well as addressing V9’s record classes, I’ve come up with the grammar changes for V10’s record structs to be sure they are consistent, and upwards compatible. Also V10’s allowance of record class as an alternative to record.

I also looked at relevant proposals in the active folder to make sure there was no potential conflict with grammar rule names and terms.

As part of “pushing down” record-related stuff, no new grammar rule contains the text record.

Bill: Assuming we go this route, that will leave two already-published sets of specs/docs out-of-synch with us:

  • The MS V9 and V10 record-related proposals
  • The on-line help/tutorials (such as here and here) that (sometimes) treat a record as an almost-first-class concept

Re the first, as Ecma isn’t going to publish V9 for some years yet, no-one will see our alternate approach to spec’ing records. As such, ASAP, it may be useful to revise the V9 and V10 proposals accordingly, so Joe Public reading them learns about the change. [Certainly, I could help with that.] Also, as MS devs write new class/struct-related proposals, they should use and build on the grammar TG2 is going to use rather than the MS-proposed rules that we’re discarding.

As for the second, it’s not obvious what might be “improved” there. Certainly, once I’ve completed my V9 proposal, I could proof that material to see if anything contradicts our new approach.

Rex

================================================================

Metadata

Metadata

Assignees

No one assigned

    Labels

    meeting: discussThis issue should be discussed at the next TC49-TG2 meeting

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions