Open Kycermann opened 2 years ago
What do you mean “use this with classes”?
You can use Object.seal with classes (and with all objects) already, since ES6.
this may be referring to https://github.com/tc39/proposal-structs
I'm not sure what the significant difference is between adding new properties and modifying existing ones. Both are problematic for values that are compared by their content, since the equality operation becomes unstable (and storage in a set has questionable behaviour), and the values will have identity (modifying one is not the same as modifying another).
I've given some examples here (https://github.com/tc39/proposal-record-tuple/issues/292#issuecomment-1030450498) of how classes (or prototypes) would be useful with records, though this depends on removing the restriction of object value members.
Clojure has long promoted the idea of using maps (records) and vectors (tuples) to define immutable constructs which could be used to model state change with an atom (state container). And even though it encourages one to start with "just data" before moving to types it defines Clojure records (not to be confused with the records of this proposal).
Clojure records are like this proposal's records only they're typed by the user. It may not be immediately obvious but it is usually helpful (for all the benefits purity affords) if something which can be spawned in the mutable realm of OOP can also be spawned in the immutable realm of FP. And just as records are the immutable realm equivalent of plain objects, it's useful to have a typed object (spawned from a normal class or constructor function) equivalent which is record like.
This supports the functional core, imperative shell pattern Clojure was designed to support. Records and tuples are a fantastic step in that direction, but its just untyped data. There is an added benefit to reusing these concepts (as Clojure does) but in the land of types, especially because of protocols, which have some aspiration of becoming first-class in EcmaScript.
I ordinarily represent my domain entities as immutables and simulate change with a state container. It is in combination with protocols that this becomes even better. With protocols, these immutable representations of real world things can become state machines.
You call the same action (a function which replaces the current representation of the typed immutable data structure with a modified copy) on an entity whose type could belong to any in a union type. The key is that not only does a swapped out representation of the entity have different data, it may sometimes have a different type. With the swapping/state container, the resulting simulation affords entities something even OOP doesn't easily afford. The ability of an entity to change its type! This alone makes modeling a state machine in FP land simpler and easier in the same way as it makes modeling undo/redo simpler and easier.
Having implemented protocols in a library and used it for wanting to do in JavaScript what Clojure does but without transpilation, I can attest to very positive firsthand experience doing exactly this for many years.
When records/tuples goes one step further (e.g. the ability to define custom-typed records and tuples) and protocols reach a mature stage too then it offers the JavaScript an exceptional environment for modeling programs using the functional core, imperative shell pattern.
This discussion needn't delay records and tuples from reaching the next stage as is, but I am hoping that implementers are told to at least consider the idea that typed records and tuples are likely coming (classes that spawn them) so that they implement in a way that allows the next step forward to be less of a do-over.
class GreenLight #{
constructor(duration){
this.duration = duration; //immutably set once
}
change() { //duck typing could be used in lieu of protocols
return new YellowLight(10);
}
}
class YellowLight #{
constructor(duration){
this.duration = duration;
}
change() {
return new RedLight(40);
}
}
class RedLight #{
constructor(duration){
this.duration = duration;
}
change() {
return new GreenLight(40);
}
}
const light = new StateContainer(new GreenLight(40));
while(true){
swap(light, change);
pause(get(deref(light), "duration"));
}
In every way these classes are like other classes except for the immutable record-like objects they spawn.
In every way these classes are like other classes except for the immutable record-like objects they spawn.
To elaborate on this, these are ultimately just record values with a custom prototype. Object.is(new GreenLight(40), new GreenLight(40))
, but !Object.is(new GreenLight(40), new RedLight(40))
.
This depends on allowing object values as members of records, which is something that is allowed in every other programming language that I'm aware of, but so far as discussed in #292, #206, #31 and elsewhere, some people in TC39 oppose this.
Yes, these are just record values with custom prototypes. Also, their constructors allow for the initializing of their immutable properties. Plus they define methods. And equality is now checked by comparing by value and type.
But the greater good would be to have this accompanied by first-class protocols as this would grant JavaScript much of what makes Clojure such a wonderful place in which to write programs.
Plus, since the functional core, imperative shell approach usually shifts entities from the mutable world to the immutable world, you need a convenient way of doing the same things you had done with side effects inside your simulation. And having the ability to implement on either side of the functional divide is a great choice to be able to make!
Anyway, there appears to be duly noted intent to move on this, eventually.
I'd like to use this with classes, where you can't add new properties but can modify existing ones. This would solve the problem of storing duplicate
{a:1}
in a set.Does this make sense?