-
Notifications
You must be signed in to change notification settings - Fork 7.3k
Node stdlib should take inherited properties into account and do so consistently #7587
Comments
Why should an implementation detail like
I think we must define common, though. I took a random example — |
What is the baggage that passing in an inherited object brings? The moment you want to make a copy of it in your own function for mutation, you'll iterate over the properties and flatten it out. I don't know which school of design you adhere to, but inheriting from A quick check — are we arguing over the same thing? I say the following code must work consistently: var DEFAULT_OPTIONS = {host: "example.com"}
var opts = Object.create(DEFAULT_OPTIONS)
opts.port = 443
new Socket(opts) Are you're saying that the caller would have to flatten |
I have a feeling we or I am not putting enough emphasis on prototypical inheritance in this discussion. JavaScript is not a _class_ical language, so all there is is inheritance from existing objects. The fact that inheritance is under-used in non-constructor cases, I can imagine, is both due to
Subclass what? I assume you meant inherit, given that JavaScript has no classes? So in other words when working with inheritance, one should inherit? 8-)
That could only be if you think your function has any job to care how the object given to you is implemented. It doesn't. It shouldn't. I give you an object with options set in properties. That's should be our contract. While our previous hot discussion was around Lodash's utilities, let's leave implementing and naming cloning functions to another day. This is about public APIs and their contracts.
Are you saying you'd rather have things be inconsistent and change sporadically dependent on some implementation detail of how default options are handled in that function? And/or specifically state that prototypes in a prototypical language are so complicated that we're going to fight with them by ensuring each plain object is run through a filter to remove its parents? The status quo is a sunken cost and, as I said, I am willing to do the work myself. I've got a vested interest in this and care for consistency and non-surprising behavior.
As I asked above, people should not use prototypes in a prototypical language? ^_- Mind clarifying for me what is your argument for the desire to disallow objects with inherited properties in (Node.js' or other's) APIs? So far I've only heard that it's because there's this |
As I've understood the "class" syntax for ES6 is only syntatic sugar, so all discussions around prototypical inheritance still apply. But in the end I'm not talking about classes, I'm talking about objects with inherited properties.
That's a little circular here. It causes extra wtf only because developers have used wtf-inducing helper functions — the idiosyncratic extends or possibly Saying it's not necessary is a little meaningless. It solves a great problem of defaults without having to use external libraries, is the core part of prototypical inheritance and a valid technique. We could say the
So there were some dudes who extended If your function wants to mutate the passed-in object, absolutely, make your own copy: function api(options) {
options = extend({}, options)
}
function extend(target, source) {
for (var key in source) target[key] = source[key]
return target
} Making your own copy has nothing to do with how the caller set up their descriptors beyond that they made them enumerable.
First, that would be the caller who would have to remember to call those functions. And secondly, you're thinking in classical terms again. This is not about classes. This is about an object with inherited properties. There's no behavior attached to this object. To remind, I'm not talking only about Surely you don't like inconsistency any more than I do, having touted it as a feature of Lodash just recently? There's work involved in making it consistent regardless. I would even say more work to ensure inheritance is stripped than to allow it everywhere. @jdalton, why are you so opposed to something that will make no difference to your code, but will make things more consistent and flexible for others? If you've so far used only objects with own properties, nothing will break or change for you. |
With all due respect, if your argument is based on a (falsifiable) statement, then that statement may get criticized. Don't take it personally.
Are you kidding me? There's a person who has an object with let's say If you're talking about someone adding enumerable properties to
That's funny, because one can say the opposite is in fact true —
Umm, that's a little contradictory. Where would that
Perfect! Mind explaining how accepting inherited properties trip up the devs using that API? The argument about someone using It's like saying because one bafoon cut his fingers off with a metal knife, everyone else should cut tomatoes with the butter knife they made in 2nd grade. If you get tripped up by inherited properties, fine, stick to whatever works for you. This is not about you. This is about doing the right thing in an API used by everyone by also following the robustness principle. The thing is, it already works fine with inherited properties in some/most of the places. All I want is to ensure it will work consistently everywhere. Don't worry, I would add automated tests to make sure it stays that way. |
As a bystander on this issue, I have been (and continue to be) struck by the lack of consideration and generosity shown by both parties in the discussion. There has to be an easier way to work this out. |
@othiym23 In my opinion, the generosity was extended by @jdalton and was depleted by @moll in a separate thread/issue which started this debate. |
I must say I'm on @moll's side here. So far the only argument against supporting inherited properties is that they're supposedly dangerous, but I haven't seen a valid rationale.
I'd really like to see how. |
As we've sidetracked into subjective interpretations, I absolutely welcome any comments on the specifics of my generosity through email. I think we both have done a fine job of avoiding ad hominem attacks with the occasional humor. Any statement or argument, mine or others', on the other hand, should never be considered sacred nor safe from criticism. Some people always take criticism of their ideas personally. Ces't la vie. So, sorry, @jdalton, you're not my target, only your beliefs. :-)
Does the standardization body have someone arguing for prototypes? If not, I think we'll meet again. ;-) I'm rather certain it's impossible to banish prototypical inheritance anytime soon. The opposite is even worse, after all — meta-regress, well described in the 90's paper "SELF: The Power of Simplicity" by David Ungar and Randall B. Smith. But come on, you may not like me and that's fine, but don't fight ideas based on who supports them. Ideas are independent from people.
The example you gave is about trusting default property descriptors when using
A side note, but never ever change anything on the exported object as a user of that library. At least not on Node.js. Node may reuse and resolve a |
I honestly do not understand. I may be thick, but I can not imagine any problems with getting a plain object that makes use of prototypes. I will read properties off of that object and I'll be happy. If I want to modify that options object, I'll go over every property there and copy it somewhere. Just like I do with an options object that does not make use of prototypes beyond If you don't want to explain it to me again, I understand. If you do, feel free to quote yourself. Or perhaps there's someone else in this thread who got what you're saying and can enlighten me.
That's only by pure luck. If you and I create two modules both dependent on module X and we both modify module X's defaults with e.g. |
I replied to @jdalton via email which made him revise his comment above, to which I shall now just add that it might not be a well known fact, but Node.js/NPM does not ensure you and I get different instances of module X if our version requirements match. It's do with resolving cyclical dependencies, and perhaps just saving unnecessary computation, that a lot of modules might end up sharing an instance: http://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders. NPM has a convenient command called If you want to discuss this further, I'd be happy to, but let's find another medium and not hijack this thread. Should we start our own debate club? ^_^ |
The discussion between you and I seems to have reached a standstill, but the issue itself is definitely open until Node.js core comes to a consensus and hopefully I can submit a few patches to make things consistent. ;-) |
Node's usage of
The whole point of defaults is being able to deviate from them. I don't see how moving them to a separate file solves anything. |
No,
Oh it does affect it. env.js console.log(process.env); test-env.js var fork = require('child_process').fork;
var options = Object.create(null); // doesn't inherit anything!!
fork('./env.js', [], options);
Object.prototype.env = {hello: 'world'};
fork('./env.js', [], options); // oops!
Are we both talking about your own application-specific defaults, rather than the function's provided defaults? @moll has provided an example before, here it is slightly modified: var DEFAULT_OPTIONS = {host: "example.com"}
var opts1 = Object.create(DEFAULT_OPTIONS)
var opts2 = Object.create(DEFAULT_OPTIONS)
opts1.port = 443
opts2.port = 80
new Socket(opts1)
new Socket(opts2) |
How is placement relevant?
I think you're missing my point, or I'm missing yours. You gave me an example of developers getting tripped up on
Would you mind providing an alternative to the example I provided, since all your examples seem to have the defaults inside the function getting called. |
The solution in search of a problem here is precisely the action against letting objects with inheritance through. If you, John, say most devs don't use them, then there's no harm letting them by; no need to take extra measurements (check hasOwnProperty etc.) to stop them. Your first accusation in this thread was that I was dealing with hypotheticals. Now we're in a situation where neither Nikolai nor me get the problem or have seen an isolated example of the "trip up" you so warn us of and try to protect the world from. Please give us an isolated code example of the problem of taking inheritance into account in such a way where the problem is only caused by inheritance from an intermediate object and can not happen if said cause is applied directly to the passed-in object. Please also be precise whose problem it is — the caller's or the callee's. A.
|
I'm not sure what you mean by inheritance being buried somewhere. An object's prototype is set at the point of its creation. If the object is created in another module, then either that module was written by you, or you're using someone else's object. In the latter case, you're likely going to have other problems than inherited properties.
I assume you mean the first snippet here. So you're saying someone might...
...and then be surprised that that instance has a property
If you mean the second snippet in the same comment, then it's not what I meant. The function is external, you have no control over how it handles defaults. |
This is what I meant by using someone else's object. If you're passing anything other than an object literal as options, then you should know what you're doing and where that object is coming from.
Again, why would you pass an object coming from libs/frameworks as options unless you know what you're doing?
Yeah but we're talking about functions that don't expose their default options (i.e. all of node's API). I gave you an example of using inheritance to provide your own defaults.
See above point. |
And my point was that it's the dev's choice to abstract or not.
I don't see much cost. If the function doesn't copy the object, there's nothing to do. If it does, then it uses |
Your alternatives, fine or not, are not an argument against using inheritance for defaults. It's great you're versatile to come up with them, but the quantity of alternatives is not evidence of anything for any one.
My friend, inheritance is an implementation detail and the default values due to inheritance form the public contract of an export, so no surprises there.
Mind backing up this statement with some links? Especially for its reasons. If you mean the thread you started to get Object.defineProperty to ignore inheritance then its, again, modifying And as I believe @seishun has also pointed out, unless you start doing hasOwnProperty before every object access (not just in options objects), a mangled
Neither testing nor code outcomes trump other in cost. I'd even argue that removing inheritance consistently carries a far higher cost than allowing it. Every function is API area, and if you think it's reasonable to expect every function that can take a plain object to ensure it doesn't ever reference anything in that object without first stripping its parents or always using
Okay, I've gone through what you've said so far to summarize. While Nikolai addressed some of them already, I'll go over them quickly:
So, thank you for linking to your code example again, but as point 4 refutes, this is not a problem due to inheritance, it's a problem due to lack of developer education. That's almost the minimal you've got to know in JavaScript. It seems to me you've got one case that once hurt you — someone extending |
this is an interesting thread that somehow summarize properly the history of JavaScript I have been asked to pay attention and read, also I've been called in cause indirectly through some tweet or link … I am not here to take any part in this conversation, I am just here to clarify with facts the current state of JS, where it comes from, and also clarify the About Object.definePropertyThe main topic of this thread is consistency but nobody reminded that This is inconsistent with It Is Not About Object.prototype !What @jdalton mentioned was simply an example where undesired inheritance can cause problems or cause weakness in the env making more stuff that meant one writable. Moreover, WeakMaps instances have Inheriting from them, specially in the Inheriting from anything that by accident has one of the descriptor properties in its chain, will doom Really nobody seems to use, or if it does is 0.1% of the world, classes or instances that inherits from anything when they mean to As summary, this very specific case is about not having to deal with a minefield every time we use the power of ES5 and these new This case has in my opinion nothing to do with the big picture considered in this whole thread so .. moving on: JS Has Been Advocated Through OwnPropertyThis is the part where JS comes from … an era where any classical definition was able to "doom" any An era where most famous book and the first widely adopted linter told everyone that loops without the slow and boring We all know in this specific thread this is not true, plus we all know that thanks to new
At the same time, this somehow drove every new entry in ECMAScript, most of them based on
But back to the very specific console.log(
Object.getOwnPropertyDescriptor(
Object.prototype,
'toString'
)
);
// will log something like
{ value: [Function: toString],
writable: true,
enumerable: false,
configurable: true } The problem is that defaults are returned as own properties because every developers expect that those Now, with a minefield or an evil attack as var descriptor = Object.getOwnPropertyDescriptor(
Object.prototype,
'toString'
);
Object.prototype.get = function evil(){};
Object.defineProperty(
Object.prototype,
'toString',
descriptor
);
// throws by specs! Who's going to write such horrendous code? Surely not me, not you, nobody in this thread, not even @jdalton to make his point :P … but it's a vector, a "bomb in the system" that can compromise logic and create new flows in a code simply redirecting them through ES6 Entries
I understand We Should Learn From The PastThe reason today most libraries, even the core, and most developers believe that At the same time, dogmas like the following:
are the reasons today we are think that Solved AnywayI don't know what's the fuss of this whole discussion or probably I got lost somewhere … but it looks that inconsistency is the key of the initial problem … what kind of option does that method accept? Here everything is related to what kind of object you pass to that method, one that inherited properties or one that has only own properties ? But why I don't read anywhere that to support both the invoked method should simply check through the always fast, working, and easy var DEFAULT_OPTIONS = {host: "example.com"}
var opts = Object.create(DEFAULT_OPTIONS)
opts.port = 443
new Socket(opts);
// where exactly IS the problem if …
function Socket (opts) {
if ('host' in opts) this.host = opts.host
} In few words I don't understand why an utility to It looks to me the problem here is a dumb object check in the used constructor, not the fact an extend should consider all inherited properties … but to close and clarify about that, following my 2 cents on the overall issue. As SummaryI really don't like loops that use If consistency is the key there is no argument against filtering by own property for the simple reason that almost everything in ES5 and ES6 will be mostly based on such assumption because developers never cared much about inheritance and inherited names. I do, and I am not alone neither here on in es-discuss ML, but the rest of the world would like to have the least surprise specially for simple operations. |
Thank you, Andrea a.k.a @WebReflection, for your thoughts and clarifications!
I apologize for that. I might've made my proposal above a little to
As much as I'd like for every JavaScript object to be safely extendable like you (and I can tell you anecdotes of my failures 6-7 years ago — in a controlled runtime, btw), I just don't think it's feasible. Ever. Enumerable or not. Hence the dogma — a principle from experience and reasoning. The reason for this is plain old property access. It will probably always walk the prototypes. It's never been only about enumerability, it's been about the fact that once the core properties are no longer the only properties on your object by default, you cannot trust any simple truthy ( And that's where it will get most of the code out there. I do remember the And so I advocate for taking inheritance into account: for consistency and less idiosyncrasies of APIs to remember. The alternative, trying to support |
@moll I am very happy with eddy.js and it brings zero real-world problems to my plate. Don't get me wrong, I do not encourage Here you have different problems, consistency, and I've already said what I think it means.
Inheritance is OK as it is, without compromising too much the intent of copying properties around … I mean, isn't the inheritance purpose to share, so why copy shared properties instead of make explicit intent on what to pass as argument? I also consider the future named function properties, where all this discussion about defaults might take another direction since inherited properties, by specs, would be less considered than own so that signature default value might occur instead. You don't want these cases in the middle of your programming, neither do I, so Once again, my 2 cents. Best Regards |
Closing this. The conversation appears to have gone stale. Can reopen if anyone feels it needs to be. |
Hey,
Inherited properties are a core part of JavaScript and can be used to great extent beyond just for classes. Unfortunately there are inconsistencies with regards to how inherited properties are handled when passed to various functions or classes in the Node stdlib. It's a case where the the implementation leaks to the public API and the main culprit is
Util._extend
.Util._extend
is implemented to ignore inherited properties of thesource
(2nd param) and that creates hidden and undocumented public API limitations and difference. Functions usingUtil._extend
will ignore inherited properties while those doing things in the traditional way withfor in
do not. The common practice of checking for truthyness withif (options.handle) ...
of course does take inherited properties into account, so the status quo is bound to remain inconsistent.I propose the API is made consistent by taking inherited properties into account everywhere.
This may entail changing
Util._extend
, too, to iterate over all properties, but fortunately it's a private function and those people who expect various Node stdlib functions to ignore inherited properties are treading on thin ice anyways.If the proposition is okay, I can submit a patch/PR with tests.
The text was updated successfully, but these errors were encountered: