Hacker News new | comments | show | ask | jobs | submit login
Prototypes are Objects (raganwald.com)
79 points by shawndumas on June 13, 2015 | hide | past | web | favorite | 34 comments



This is an old debate that has been resolved (at least back then) since what is called "the Treaty of Orlando" at OOPSLA 1987; here is a public paper written afterward:

http://web.media.mit.edu/~lieber/Publications/Treaty-of-Orla...

Abstract:

> For the past few years, researchers have been debating the relative merits of object-oriented programming languages (OOPLs) with classes and inheritance as opposed to those with prototypes and delegation. It has become clear that the OOPL design space is not a dichotomy. Instead, we have identified two fundamental mechanisms, templates and empathy, and several different independent degrees of freedom for each. Templates create new objects in their own image, providing guarantees about the similarity of group members. Empathy allows an object to act as if it were some other object, providing sharing of state and behavior. Smalltalk, Actors, Lieberman''s delegation system, Self, and Hybrid each take differing stands on the forms of templates and empathy. Some varieties of template and empathy mechanisms are appropriate for building well-understood programs that must be extremely reliable; others are better suited for rapidly prototyping solutions to difficult problems. The differences between languages designed for each of these application domains can be recast as the differences between support for anticipated vs. unanticipated sharing. One can even ascribe the ascent of object-oriented programming to its strong support for extension instead of modification. However, many kinds of extension still remain difficult. The decomposition of an object-oriented language into template and empathy mechanisms and the degree of support for extension provided by the forms of these mechanisms provide a solid framework for studying language design.


"Empathy? Empathy? We don't need no stinkin' empathy!"

Uh, perhaps you meant "delegation"? No one uses the term "empathy" in reference to any OOP languages AFAICT except the authors of that non-text searchable paper written in 1987 that you reference. Even if _they_ used the term "empathy" it would have been better to use "delegation" in describing the paper, because to not do so reduces the empathy of your audience.

[I am thorry, I could not resithd, but muth go now becauth I have an appointment with the delegation of the ambatthador at the the U.S. empathy].


Mixins need to be used with great care in dynamically typed languages. It's extremely easy to introduce dependencies on symbols in the class you're getting mixed into, and in larger projects it's not difficult to get name clashes between methods defined in different mixins, so that earlier mixins get clobbered by later mixins.

From my experience, I'm not a big fan of mixin-heavy development. Mixins need a lot of discipline. A mixin probably shouldn't exist if it doesn't have at least two, if not three different target classes it is mixed into.

I've seen mixins used for almost evil things in Rails. When mixins start adding before filters to controllers, disturbing amounts of magic can occur. You can end up needing to research a whole dependency dag and details of its implementation before you can understand a single method that may not refer to a mixin by symbol at all.


TFB(ook) by TFA(uthor) of TFA(rticle) discusses the issues with open recursion, and describes a pattern for using ECMAScript 2015 symbols to address this exact problem:

    const books = Symbol(“books”);

    const BookCollector = {
      addToCollection (name) {
        this.collection().push(name);
        return this;
      },
      collection () {
        return this[books] || (this[books]= []);
      }
    };


Depending on the use case the name clashing problem can be easily solved. For instance, in Lua the way I solve it is by simply letting multiple mixins define the same function, and whenever that function is called anywhere it triggers all of them. For game development this is extremely useful since usually when you have something like object:takeHit(attacker), multiple mixins that do different things in relation to getting hit (damage calculations, effects, animation, physics for pushback, and so on) define the takeHit function, but each of those different concerns is well hidden into their own separate mixin/module.

Of course I agree with you that mixins need a lot of discipline and I specifically follow the 2-3 different target classes rule. It's really really easy to fall into the trap of making everything a mixin but this is really the wrong way to go about it.


Self really had a nice solution to the issue: multiple named prototypes, properties not found on the root object would be looked up in all prototypes, and an error would be raised if multiple prototypes matched (requiring explicit resolution on the root object).


They work well enough in statically typed languages, e.g. Scala, but that is more like diamond-avoiding linearized multiple inheritance.


"JavaScript’s choice to build OOP out of simple parts–objects, functions, and properties–makes the development of new ideas possible."

They did things like every other language basically. Everything is a pointer, and types are just abstractions we placed on things. So Functions, Objects, Properties can be passed around just like any other variable or even merged together fairly easily. Some languages hide this from you.. some require that you ask very nicely if you do things like this(pascal).. and others simply don't care what you do(c++).


While the observations I am about to make likely will be down-voted by some/many in this forum, perhaps even be viewed as heresy, I am compelled to present them on the chance that it helps some to understand what JavaScript objects truly are.

TL;DR JavaScript "objects" are nothing more than a <a href="https://en.wikipedia.org/wiki/Hash_table">Hash map</a>.

From the article:

> Prototypes in JavaScript are “just objects,” and since they are “just objects,” we can add, remove, or modify methods of the prototype by adding, removing, or modifying the functions bound to properties of the prototype.

They are not "just objects." In fact, symantically they are nothing more than a hash map (a.k.a. hash table, dictionary)[1]. The above quote from the article clearly indicates this fact as, if we peform three simple keyword substitutions, the sentence reads thusly:

Prototypes in JavaScript are “just hash maps,” and since they are “just hash maps,” we can add, remove, or modify values of the prototype by adding, removing, or modifying the values bound to keys of the prototype.

Attempting to think of JavaScript objects in the same manner as what most mainstream "OO" languages semantically provide is simply wrong. "Prototypical inheiritance" is nothing more than a shallow copy of an associative array. By its very definition, that is _all it is_.

Now, IMHO there is nothing wrong with a hash map. There's nothing right about it either. Those sentiments imply judgement, whereas what I have said above is simply analyzing the nature of the subject. The fallacy in calling JavaScript "objects" objects lies in deceiving one's self into thinking that the same semantic guarantees OO languages provide can be done in native JavaScript. The quicker a developer accepts that JavaScript objects are simply hash maps, the quicker they can understand the semantic contract being proffered by the environment _and_ be able to work with it as best as possible.

1 - http://www.w3schools.com/js/js_object_definition.asp


As others have noted, the disclaimer detracts from your message. Just because people often say “This will be controversial, but...” does not mean that it adds to your message. It does not lower the reader’s defences against controversial ideas.

What it actually does is appeal to people who are already predisposed to heterodoxy. For example, if I begin a comment with the statement:

“This is going to bring the Men’s Rights Activist sea lions out of the woodwork, but...” What I am doing is appealing to people who are already predisposed to dislike the MRA movement and its adherents. I am absolutely not trying to appeal to people leaning that way to consider my message carefully and possibly soften their stance.

Thus, statements like this tend to be divisive and polarizing, which runs 100% counter to a culture of open minded discussion.

Far better, IMO, to sneak up on people who think that JavaScript objects are pretty-much the same thing as Smalltalk objects, and show the differences one-by-one, building to a grand reveal that--whoa--there is a big conceptual difference between a dictionary that happens to have methods and a fully encapsulated actor that responds to messages.

TFA links to a post discussion this: http://raganwald.com/2015/05/11/javascript-classes.html

JM2C, YMMV, &c.


Points well presented, noted, and internalized for future use. :-)

Thank you for taking the time to reply. In attempting to preface the content as potentially being controversial, I succeeded in only being trite (or droll or both).

Learning is fun. Embarrassing sometimes, but fun.


Objects in Javascript, while very similar to HashMaps, are not Hashmaps.

` "Prototypical inheiritance" is nothing more than a shallow copy of an associative array. By its very definition, that is _all it is`

No, when you access a property on an object, and that property does not exist on the object itself, Javascript will look for the property on the Object's prototype and the prototype's prototype, etc...

This is very different then a dictionary, where you now have the ability to 'shadow' a property or to change the value of a key in a prototype and have it reflected in all children (which is the exact opposite of a shallow copy).


I believe what you describe can be also described as performing a shallow copy on the prototype which includes the entries from previous copies of the hash map referenced in prior statements.

Unless what transpires is a "back link" to prior definitions such that additions to those prototypes are reflected in subsequent references. Quite frankly, I don't recall off the top of my head which way compliant EMCAScript implementations behave.

> This is very different then a dictionary, where you now have the ability to 'shadow' a property or to change the value of a key in a prototype and have it reflected in all children (which is the exact opposite of a shallow copy).

A dictionary which is shared across all instances would reflect this behaviour, true, but when a dictionary's contents are copied, changes made to the source would not be reflected in the copy (for "first level" key value pairs). I'm pretty sure that adding an indirection to a JavaScript prototype definition (such as placing a hash map in the prototype) would have _its_ key value pairs shared across instances.

But I could very easily be wrong about that.


> Unless what transpires is a "back link" to prior definitions such that additions to those prototypes are reflected in subsequent references. Quite frankly, I don't recall off the top of my head which way compliant EMCAScript implementations behave.

It is:

    var a = {}; var b = {__proto__: a}; console.log(b.x); a.x = 5; console.log(b.x);
(prints undefined then 5)


There are some other points that you are missing, like how do constructors work? If JS objects are just glorified hash maps, why can I write a constructor function to do some stuff when the object is instantiated? The object literal syntax that looks very much like the "hash map" you are describing is exactly the same as creating a new object that extends the Object prototype. In fact, that's pretty much what's going on when you type `{}`.

JavaScript is absolutely an object-oriented programming language, but it's unusual because it's also pseudo-functional, in that functions are first-class citizens. However, functions are also objects. When you type `function` in JS, that is generating an object with the `Function` prototype. You can extend this prototype and call methods on `Function`. That's how Ember.js makes its computed property syntax work, so you can just tack on `.property()` to the end of your function. This would not be possible if JS worked given the semantics you described, because in that story a function is just a function, and not an entire object. JS hides these gory details from you to simplify your experience. Most of the time, you shouldn't have to worry about these kinds of things, but it's fascinating to learn about because there's no other language that works quite like JS.


> If JS objects are just glorified hash maps, why can I write a constructor function to do some stuff when the object is instantiated?

There's no big reason for that, and Object.clone doesn't give that hook, you have to use a wrapper function instead.

> JavaScript is absolutely an object-oriented programming language, but it's unusual because it's also pseudo-functional, in that functions are first-class citizens.

What's unusual about that?

> it's fascinating to learn about because there's no other language that works quite like JS.

That can be said about pretty much any and every language out there.


> While the observations I am about to make likely will be down-voted by some/many in this forum

You are likely to get down voted for this alone but since you are new here, I suggest you read the HN guidelines here: https://news.ycombinator.com/newsguidelines.html


Prefacing a potentially contraversial position is a well established practice to inform a reader that what follows may not be readily accepted.

In retrospect, mentioning down voting was trite. Thank you for being conscientious enough to point this out to me.


I would argue that objects are nothing more than a uniquely identified run-time entity. With that identity, they can easily have their own encapsulated state (since their identity can be used as an address or key into a global map), and can be associated with behavior that is selected dynamically. Compare this with the dual of an object, the value, whose identity is determined by its structure alone (e.g. 1 and 1 are the same values), and so does not allow for state, nor does it allow for dynamic behavior selection without first turning them into objects (aka boxing). The implications of object-ness lead to certain styles of computation (noun-based) just as they do for values (expression oriented and functional).

Objects existed in PL before the more limited kind in Java or even C++, not to mention the powerful but very Java-unlike object systems Self and CLOS that directly influenced JavaScript's design from the beginning.

The only fallacy, I think, is ascribing a notion of objects based on a couple of mainstream data points. Objects are much richer than that.


(Side note: it turns out I can't delete the other message. I must have hallucinated having that ability last night.)

> I would argue that objects are nothing more than a uniquely identified run-time entity. With that identity, they can easily have their own encapsulated state (since their identity can be used as an address or key into a global map), and can be associated with behavior that is selected dynamically.

I largely agree and believe this is pretty consistent with the commonly accepted definition of an object in the Comp Sci sense. There are a couple examples which immediately come to mind (Smalltalk, Objective-C) along with other languages (Ruby, Perl, Python) having the ability to selectively provide equivalent behaviour.

Where I previously said "largely agree", the point where I disagree is when you state "objects are nothing more than a uniquely identified run-time entity." Objects also have associated with them a set of messages which are either intrinsic to the environment's definition of what minimally constitutes any object's behavioural contract, identified by the definition of the object's type, or some mixture of the previous two along with a mechanism to resolve message dispatching.

> Compare this with the dual of an object, the value, whose identity is determined by its structure alone (e.g. 1 and 1 are the same values), and so does not allow for state, nor does it allow for dynamic behavior selection without first turning them into objects (aka boxing).

Here, I think we disagree as not all languages treat values as being disjoint from objects. I realize you mention boxing, which languages such as C# and Java perform for _their_ definition of "plain old data" types, but this is not the same as languages such as Ruby, which represents all values as objects intrinsically.

> The only fallacy, I think, is ascribing a notion of objects based on a couple of mainstream data points.

The fallacy which I mentioned is in thinking that a person has the same checks in JavaScript applied to collaborations as is expected in other OO languages/environments. I view this as a direct consequence of JavaScript objects being hash maps. Supporting this view is:

> A JavaScript object is an unordered collection of variables called named values. (source: http://www.w3schools.com/js/js_object_definition.asp)


> Where I previously said "largely agree", the point where I disagree is when you state "objects are nothing more than a uniquely identified run-time entity." Objects also have associated with them a set of messages which are either intrinsic to the environment's definition of what minimally constitutes any object's behavioural contract, identified by the definition of the object's type, or some mixture of the previous two along with a mechanism to resolve message dispatching.

Good well-developed object abstractions have that feature, but that the intrinsic "objectness" that leads to such a feature being desirable does not. Plenty of us remember working with proto-objects that did not have these features, but the nature of our objects drove us to re-discover this feature on our own (e.g. by rolling your own v-tables for a pointer to a struct in C!).

> Here, I think we disagree as not all languages treat values as being disjoint from objects. I realize you mention boxing, which languages such as C# and Java perform for _their_ definition of "plain old data" types, but this is not the same as languages such as Ruby, which represents all values as objects intrinsically.

Again, I'm going for a definition of objectness. That Ruby treats values as objects isn't necessarily a good thing, and programmers will definitely want to often treat these as values anyways even if the language is resistant to that; e.g. somehow getting 1 == 1 to evaluate as true rather than false! Valueness is just as important as objectness, and in most programs we use both to certain degrees (even if our languages support one over the other).

> JavaScript applied to collaborations as is expected in other OO languages/environments.

We can argue whether it is a good design choice compared to other OO languages/environments, but it definitely makes them "objects."


I want to spend the time to provide a cogent response and it is late where I am at. So this is a "todo" response which will be deleted with a proper one ASAP.


sean,

>implications of object-ness lead to certain styles of computation (noun-based)

i understood that

>just as they do for values (expression oriented and functional).

but wasn't sure what you meant there. could you elaborate?

thanks.


Values lack intrinsic identity, they are basically the antithesis of objects. So say you have a function that computes a value...given the same arguments it will always compute the same value, even if it is not exactly the same one (e.g. 1 and 1 computed via different operations on the machine). The key defining aspect of functional programming is its value orientation (just like the key defining aspect of OOP is its object orientation). When working with values, you'll find that different abstractions and designs make more sense, like the use of functions or expressions for everything, while OOP is more biased to statements and commands.


I think you're confounding the idea of a "dictionary" with the specific data structure of hash map/table. Not all dictionaries are hash maps. There's nothing in the spec that says the dictionary-like properties of an object must be implemented as a hash map. I'm fairly certain that in at least V8 they are not hash maps.


Yes JavaScript objects are weak (allow more, too much) compared to legitimately called OOP ones. But is it really different ? When I see an anonymous function I see a map, lambda calculus classes made me see the world through this choreography of maps. Sure they don't mutate, they rewrap, shadow, etc etc. And JS appears as piece-wise OOP, you have the scope-inheritance mechanism in place. OOP with the larger semantic weight, forbid some things, at least it felt a lot more constrained and a burden in my experience, turning things into class hierarchy silos and unnecessary patterns. What are real OOP semantic guarantees ? static types ?


My main point was not an "attack" on JavaScript objects. Instead, it was to illuminate that how many people write/speak of JavaScript objects use a frame of reference which simply is incorrect.

Your question:

> Yes JavaScript objects are weak (allow more, too much) compared to legitimately called OOP ones. But is it really different ?

Can certainly spark an intriguing conversation indeed. Without making this a treatise, having the likelyhood of mistakes an on-the-fly one would produce, allow me to address this in parts.

> Yes JavaScript objects are weak (allow more, too much) ...

This is the basis of my assertion that semantic guarantees do not exist in this language. Remember, the article's author makes the statement:

> ... modifying the functions bound to properties of the object. This differs from most classical languages, they have a special form (e.g. Ruby’s def) for defining methods.

Significant in this quote is that, in this example, Ruby uses a keyword in its identification of a method. As such, use of the symbol identified by the keyword def allows the Ruby environment to provide a semantic guarantee that its use as a method definition is within the allowable bounds of the language. While this may not sound like much, when presented with thousands of symbols/variables in many systems, the cognitive load is reduced while simultaneously some portion of semantic use is verified by the environment.

> ... compared to legitimately called OOP ones.

I beleive a better categorization of JavaScript would be as "object based", with a non-trivial functional leaning. But that's just me.

> But is it really different ?

Yes, but different !== bad.

> When I see an anonymous function I see a map, lambda calculus classes made me see the world through this choreography of maps. Sure they don't mutate, they rewrap, shadow, etc etc.

IMHO, the significance of immutability is key with the core concepts of functional programming (to which it owes its existence to lambda calculus). Transformations with the certainty that <a href="https://en.wikipedia.org/wiki/Referential_transparency_(comp... transparency</a> is enforced is the foundation which reasoning about complex function interactions is made possible. So when an environment allows effectual modifications _at any point in a call chain_, there is no provable way to show what state a system is in based on the call flow.

This is the dagger with which mutable collaborations stab provability. Of course, all "OOP languages" I am aware of support this style of programming, which is the topic of another thread I suppose.

> What are real OOP semantic guarantees ? static types ?

Many times, but not always. To me, a semantic guarantee is along the lines of the Ruby example explored above. It is when a language's environment provides the developer with feedback when use of a symbol is not conformant with its definition (if known). What JavaScript specifically does not provide is any assistance in identifying this type of issue (albeit trivial):

var foo = doSomethingAndGiveMeBackAFoo ();

// Is this correct? foo.a ();

// Or this? foo.a + 1;


for any particular reason are you returning "this" in your rename function?


Probably for chaining


Definitely for chaining.


Chaining. Definitely for chaining.


Chaining. Chaining. Definitely for chaining.


TypeError: Cannot read property 'reply' of null


//TODO: Make reply a method.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: