Hello Ilija and Larry, You both touch upon some interesting and similar thoughts, and it may be worth sharing how I arrived here; at least so we have some shared context:
Personally, I feel that classes are quite bloated with features and what feature works with what is quite confusing for new developers; I would rather see features removed than added to them at this point. This isn't why I chose the "record" keyword or a new syntax, to be clear. However, it was one of many strong reasons as to why I felt it would be an ok deviation from "tacking on more features to classes." One of the main reasons for the alternative creation syntax is because I felt that "new" was misleading at best, and just plain wrong at worst. It is also why I chose "&", to make it clear you are not getting "a new one" but one that "just happens to exist with the values you asked for." I'd be open to a different keyword or something else entirely. It's just that "new" is the wrong one for records. I did explore "data classes" to a degree, and I can see why Ilija created the new "mutating" syntax for their RFC. It gets really weird, really fast. Records are the other side of structs, though. They are (nearly literally) arrays with a class entry on them, and thus, essentially, typed structured arrays with behavior. In fact, I was originally going to pitch it as such, but decided it would either stand on its own or not; but it is what they were designed to be from the beginning; hence the short declaration syntax. In other words, I see this being used anywhere you'd normally use a structured array but want some type safety, without all the boilerplate of classes or dealing with equality. For example, DTO's, configuration, etc. They solve a different problem than Ilija's structs, which makes more sense for collections, but I see records as being good candidates for values in those collections. As I shopped this RFC around to coworkers, old coworkers, other maintainers on the projects I work on, and (nearly) random strangers on the internet, it became clear that people liked it and understood them, but wanted more power. Things like better custom (de)serialization than what we have with readonly classes, custom initializers for computed properties, etc. Furthermore, as I thought about these new features for records, I also thought about how people would use them. They would most likely start with a simple record (or not), but when changing them, I also thought about the diffs they would create in code reviews. In fact, this is a large reason behind the rules for traditional constructors. Adding a traditional constructor to an already existing record should look exactly like that in the diff, without shifting around a bunch of properties. Eventually, we ended up with the RFC you are reading today. (To be 100% transparent, some people also thought it was pointless and wondered what was wrong with readonly classes, but I'll come back to that). Now, with that context in mind... On Sun, Nov 17, 2024, at 08:21, Larry Garfield wrote: > Plus, having another fixed type creates questions any time a new feature is > added. I think this would happen even if I scoped out some of the RFCs you hinted at. I don't think there is any way around this. Adding new features complicates future features, and it doesn't matter where you put them. Or rather, it matters, but not as much as you'd think. For example, my nameof RFC is basically on hiatus until we have pattern matching. We simply lack the grammar necessary to do it correctly, and it doesn't seem like the place to define that grammar because then it may pigeonhole pattern matching. I think that is fine, but these are normal things to go through when working on an RFC, in my limited experience. My point is, we constantly have to make this decision process, and we just have to 'figure it out' as we go. On Sun, Nov 17, 2024, at 01:15, Ilija Tovilo wrote: > I personally do not think immutable data structures are a good > solution to the problem, and I don't feel like we need another, > slightly shorter way to do the same thing. On Sun, Nov 17, 2024, at 08:21, Larry Garfield wrote: > As Ilija notes, immutable objects are not always the answer. I like them, > and use them frequently, but they're not always appropriate. And we already > have them with either readonly classes or now private(set) (which is close > enough to immutable 99.4% of the time) To be clear, this is another tool in the developer's toolbox and not meant to replace classes (even readonly classes). They are strictly immutable value objects, which makes sense for numbers, time, custom values (see: the UserId example in the RFC), etc. These aren't as good for generic collections like maps, sets, vectors, etc. or even things like services or controllers. For these types of things, classes (or structs) make more sense. Like Ilija mentioned in their email, there are significant performance optimizations to be had here that are simply not possible using regular (readonly) classes. I didn't go into detail as to how it works because it feels like an implementation detail, but I will spend some time distilling this and its consequences, into the RFC, over the coming days. As a simple illustration, there can be significant memory usage improvements: 100,000 arrays: https://3v4l.org/Z4CcV 100,000 readonly classes: https://3v4l.org/1vhNp and what we would expect from records: https://3v4l.org/4nYXG which is on par with the array example. Naturally, real life won't see that kind of reduction in memory consumption (nobody is creating an array of all the same items, most of the time), but hopefully it gives you an idea of what can be gained. On Sun, Nov 17, 2024, at 08:21, Larry Garfield wrote: > I can see the benefit of an inline constructor. Kotlin has something > similar. But I can see the benefit of it for all classes, even service > classes, not just records. (In Kotlin, it's used for service classes all the > time.) I think this would be a good future scope. If people like it, an RFC to extend it to regular classes makes sense. > There's already been an RFC for clone-with that works on any object; it just > never made it to a vote. I could see an argument for an even more dedicated > syntax (eg, eliminate "clone" and just do "$foo with (bar: 'baz')"), but > again, useful on all objects, not just records. I feel like this is similar to my nameof RFC and doomed from the start. Classes are just too featureful to pin down what a "with" actually means. The discussion went into depth on this, and nothing definitive came out of it (IMHO). In a sense, records get a "fresh start" and can define exactly what it means and how it can be used. > The alternate creation syntax... OK, this one I can't really see a benefit > to, and Ilija already noted it may cause conflicts. And from Ilija: > A small note: The $test = &Test(); syntax is ambiguous, as it's > already legal. https://3v4l.org/CE5rt I originally had something about this in the RFC but removed it at the last minute. I'll add it back! For now, it will live in "open issues" in case someone has a better idea. As to the reason for it, I covered it briefly above, but as a reminder, it boils down to my reluctance to use "new" to get a record. If anyone has any better ideas, I'm open to it. And again, from Larry: > Value-based strong equality, in cases where I want that, I also want to be > able to control it. That goes back to Jordan's operator overload RFC, and > specifying a custom == and <=>. I'd rather just have that. I originally wanted to add operators. For (I hope) obvious reasons, I decided to just wait for them. I think something like records (with value semantics built-in) would be much more palatable for people worried about the "abuse" of operators. I'm just going to steer clear of that topic and leave it "undefined" for now--with the expectation that someone (Jordan?) may come along to define operations on objects. > Value-style passing is the really interesting one, but I want to be able to > use it without being forced into all of the other features here. I don't think there is much way around it. You either need a special syntax (structs) or immutability (records). For regular classes, there is always "==" if you have control over how they are compared. Sadly, I don't think this is possible for regular classes, but I could be wrong. And... that's a book. I'm really sorry about the length of this email, but hopefully I've addressed both your questions and concerns as best I can. Sincerely, — Rob