Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Inherited enumerated properties #52

Open
moll opened this issue Jul 2, 2017 · 1 comment
Open

Inherited enumerated properties #52

moll opened this issue Jul 2, 2017 · 1 comment

Comments

@moll
Copy link

moll commented 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.

var a = {name: "John"}
var b = Object.create(a); b.age = 42
var nameA = a.name
var nameB = b.name

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:

function printName(obj) { console.log(obj.name) }

function printAgeAndName(obj) {
	var {age, ...rest} = obj
	console.log(obj.name)
	printName(rest)
}

var person = {name: "John"}
var agedPerson = Object.create(person); agedPerson.age = 42
printAgeAndName(agedPerson)

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:

class Url {
	constructor(href) { this.href = href }
	toString() { return this.href }
}

Object.defineProperty(Url.prototype, "path", {
	value: function() { return this.href.split("?", 1)[0] },
	configurable: true, writable: true, enumerable: true
})

var url = new Url("foo/bar?baz")
for (var key in url) console.log(key)

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. 😇
@moll moll changed the title Inherited enmerated properties Inherited enumerated properties Jul 2, 2017
@jarmo
Copy link

jarmo commented Jul 2, 2017

I agree with @moll on this one.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants