You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.
I've been watching this proposal for a while now fearing that it would ignore the Liskov Substitution Principle, cause silly bugs in code that one would otherwise presume to behave as their ES5 counterpart and not align with the intent of ES6 non-enumerated class members. I'll go over the arguments in detail, but they all ask why the implementation of object spread is in own-properties terms — Object.assign — and propose that's not a good choice.
Liskov Substitution Principle
LSP, I'd argue as one fundamental pillar of object orientation, states that any object must be replaceable by its subtype. In a prototypical language such as JavaScript, an object inheriting from another is a subtype of the other. In plain JavaScript, that holds pretty much in all cases — it surely holds for regular property access and it'd be silly if it didn't. That is, the following two name variables are equivalent.
However, if we interject object rest spread there, let's say in some intermediate function, we'll break LSP and introduce a hard to spot bug for no benefit:
Mind you, printAgeAndName is doing the right thing with its intent of passing a supertype to printName, that is, a type without the age property (regardless of where age sits on the prototype hierarchy). It is NOT doing the right thing, however, by losing name entirely. Perhaps previously, without rest properties, printAgeAndName didn't care about removing age before passing it on as-is, and that worked, too.
But now what could've been an innocent refactoring, turned out to be API breakage dependent on the implementation of ...rest. I wouldn't expect the regular Joe to realize that. It's perfectly reasonable to imagine ...rest being a shorthand for typing all the other properties out, and if you'd done that, you'd have gotten their proper, inherited values. After all, you already know the "type" or structure of the record you're dealing with when you use the ...rest syntax. It's not a map of random keys and values.
ES6 non-enumerated class members
At some point ES6 class properties were made non-enumerable. Presumably to allow their data properties to be enumerated, skipping methods. That allows for record-like classes to easily written and used like plain objects, enabling lazy computation:
The for-in enumeration will list both href and path, but compute path lazily when accessed (memoization left as an exercise to the reader). Now imagine a situation similar to the printAgeAndName I brought up above. A function that handled this memoizing class just fine runs it through ...rest. Or perhaps to just make a copy for mutating. We'll have, again, lost enumerable properties that were implemented on the prototype for efficiency.
Summary
I'd argue the only valid criteria for inclusion of a properties should be its enumerability.
It would be consistent by adhering to the Liskov Subtitution Principle.
It would be aligned with the grain of a prototypical language.
It would be aligned with property access
It would allow objects or record with behavior (the definition of object orientation in the first place) for lazy computation as displayed in the Url example above.
Have other good design qualities that I can't be bothered to go in to now. 😇
The text was updated successfully, but these errors were encountered:
moll
changed the title
Inherited enmerated properties
Inherited enumerated properties
Jul 2, 2017
Hey,
I've been watching this proposal for a while now fearing that it would ignore the Liskov Substitution Principle, cause silly bugs in code that one would otherwise presume to behave as their ES5 counterpart and not align with the intent of ES6 non-enumerated class members. I'll go over the arguments in detail, but they all ask why the implementation of object spread is in own-properties terms —
Object.assign
— and propose that's not a good choice.Liskov Substitution Principle
LSP, I'd argue as one fundamental pillar of object orientation, states that any object must be replaceable by its subtype. In a prototypical language such as JavaScript, an object inheriting from another is a subtype of the other. In plain JavaScript, that holds pretty much in all cases — it surely holds for regular property access and it'd be silly if it didn't. That is, the following two name variables are equivalent.
However, if we interject object rest spread there, let's say in some intermediate function, we'll break LSP and introduce a hard to spot bug for no benefit:
Mind you,
printAgeAndName
is doing the right thing with its intent of passing a supertype toprintName
, that is, a type without theage
property (regardless of whereage
sits on the prototype hierarchy). It is NOT doing the right thing, however, by losingname
entirely. Perhaps previously, without rest properties,printAgeAndName
didn't care about removingage
before passing it on as-is, and that worked, too.But now what could've been an innocent refactoring, turned out to be API breakage dependent on the implementation of
...rest
. I wouldn't expect the regular Joe to realize that. It's perfectly reasonable to imagine...rest
being a shorthand for typing all the other properties out, and if you'd done that, you'd have gotten their proper, inherited values. After all, you already know the "type" or structure of the record you're dealing with when you use the...rest
syntax. It's not a map of random keys and values.ES6 non-enumerated class members
At some point ES6 class properties were made non-enumerable. Presumably to allow their data properties to be enumerated, skipping methods. That allows for record-like classes to easily written and used like plain objects, enabling lazy computation:
The
for-in
enumeration will list bothhref
andpath
, but computepath
lazily when accessed (memoization left as an exercise to the reader). Now imagine a situation similar to theprintAgeAndName
I brought up above. A function that handled this memoizing class just fine runs it through...rest
. Or perhaps to just make a copy for mutating. We'll have, again, lost enumerable properties that were implemented on the prototype for efficiency.Summary
I'd argue the only valid criteria for inclusion of a properties should be its enumerability.
Url
example above.The text was updated successfully, but these errors were encountered: