5 ms·
Like your input a lot. > All records are well-behaved as collection members; not all data classes are. > All records can be safely serialised and deserialised
by scns 4y ago
Like your input a lot.
> All records are well-behaved as collection members; not all data classes are.
> All records can be safely serialised and deserialised; not all data classes can.
> All records can be deconstructed and reconstructed; not all data classes can.
Can you give examples please?
- pron 4y ago> All records are well-behaved as collection members; not all data classes are. This is due to immutability. A var in a data class constructor means that the object's hashCode may change after its construction, and so mutating such an object may break collections that rely on hashCode to locate members, such as HashMaps and HashSets. > All records can be safely serialised and deserialised; not all data classes can. This is because records do not have any state beyond that which is constructed by the canonical constructor, which is never bypassed. When serializing a record, its components are serialized. When deserializing one, the only way to construct the record is by passing the deserialized components to the canonical construct, which then validates them. Deserializing an object with hidden state bypasses the constructor and can inject inconsistent, potentially harmful, data into it. > All records can be deconstructed and reconstructed; not all data classes can. A record is its components (and type; this is why they're nominal tuples). When you deconstruct a record with a pattern, you get its full state. If you put it back together you'll get an object with the same data as the original.
- ohgodplsno 4y ago>A var in a data class constructor Just because you can, doesn't mean you should. I can count on one hand the number of data classes with vars in our codebase of 500k+LOC, and they are specialized implementations for processes that would otherwise entail lots of copying and awful performance, whereas mutation just works.
- pron 4y agoThat is the exact observation I was referring to! You can make that desired behaviour into a guarantee that the runtime and any other code can rely on without giving up much. So even though most data classes could be records, the construct still doesn't make the necessary guarantees. Because those times where data objects require mutation are not numerous, the benefit of reducing their syntax is small. So Java was able to give the syntax benefits in almost all of the same cases, and additionally it was able to make powerful behaviour guarantees.
- Tainnor 4y agoIf you follow that logic to its conclusion, it means that you should just replace your Java code with Haskell (or at least OCaml). These are fine languages, but Kotlin is known to be more pragmatic. There are some footguns, make sure to be aware of them.
- pron 4y agoOCaml and Haskell are very different; OCaml is much closer to Java than it is to Haskell. Java is, or soon will be, a nominally-typed ML-ish (which isn't saying all that much given how many languages were inspired by ML). You could say that one difference between Haskell and OCaml/Java is that OCaml and Java have algebraic data types, while Haskell has only algebraic data types. The only way my logic could lead to the conclusion that the Haskell approach is always advisable is if you mistake "ADTs are very useful" to mean "anything beyond ADTs is harmful", which is something that Java or OCaml don't say.
- Tainnor 4y ago> Java is, or soon will be, a nominally-typed ML-ish (which isn't saying all that much given how many languages were inspired by ML). Sorry, but I call bullshit. As long as reflection is a huge part of major frameworks, libraries are loaded at runtime and mutability is the default for basically everything... there's just a world of difference between the two.
- 4y ago
- hota_mazi 4y agoAll you're saying is that records are more limited than data classes. You see that as a strength, I see it as a restriction because sometimes, I do like the extra flexibility of adding mutable state to my value objects. And I know better than altering the value of hashcode after creation, although this only matters if you've put these objects in a hashing container, so it's not even a universal risk.
- setr 4y agoEnums are also more limited than classes, yet they have purpose/place. In fact, that’s the general purpose of types — to reduce capabilities to gain access to guaranteed capabilities, which lets you reach compiler-verified logic rather than simply programmer-verified / unit-test-verified logic. Of course, it only matters if something utilities those guarantees, which I don’t know for kotlin, but C# uses records to expand out the LINQ interface quite substantially/conveniently for value comparison (mainly made convenient when you also have anonymous records)
- pron 4y agoI'm saying that records are a semantic features, while data classes are not. In the context of records, "the extra flexibility of adding mutable state" makes as much sense as saying that sometimes I like the flexibility of adding new instances to my enum classes on the fly. An enum is an enum to guarantee a fixed set of instances in the type. That records and enums are more constrained in their behaviour than ordinary classes is the reason for their existence, because they don't do anything an object of a regular class can't do. Data classes, on the other hand, exist for syntactic brevity. The difference between the features is not a difference in degree but a difference in kind.
- scns 4y agoThank your for these clear explantions and showing how problematic mutability can be there. As my siblings allready stated this is easily mitigated but still good to know.
- marwis 4y agoBut records don't actually offer such guarantees: record MutableRecord(ArrayList<String> list) {} var r = new MutableRecord(new ArrayList<>()); System.out.println(r.hashCode()); // -> 1 r.list().add("foo"); System.out.println(r.hashCode()); // -> 101605 I sort of hoped they would freeze hashcode but they don't.