stampit-org / stampit

OOP is better with stamps: Composable object factories.
https://stampit.js.org
MIT License
3.02k stars 103 forks source link

Should methods really be called prototype? #16

Closed pluma closed 11 years ago

pluma commented 11 years ago

The naming of fixed aside, fixed.methods acts as the de facto prototype of objects created by the factory:

var stamp = stampit();
var instance = stamp();
console.log(Object.getPrototypeOf(instance) === stamp.fixed.methods); // true
console.log(stamp.fixed.methods.isPrototypeOf(instance)); // true

So shouldn't methods be called prototype or proto or something to that effect? After all you can pass in other things than methods (e.g. getters, setters and values) and they will behave just like they would on a prototype.

I guess a counter-argument is that stampit doesn't really have inheritance (types are instead composed) and prototypes don't seem to provide a sane way to support multiple inheritance.

ericelliott commented 11 years ago

No. .methods() and .state() both represent two different kinds of prototypes. .methods() are for the delegate prototype, while state is for the exemplar prototype (clone source). It has not been known as a prototype in the past, but .enclose() functions can also be seen as prototypes for initializer functions: That is, functions that are applied to every new instance of an object.

They have specific names to clarify the best use-case for each.

After all you can pass in other things than methods (e.g. getters, setters and values) and they will behave just like they would on a prototype.

Getters and setters are methods, and using values on a delegate prototype is an anti-pattern, for two reasons:

  1. It's really easy to cause accidentally shared state (since mutating an array or object property on a delegate prototype will mutate that property for all instances).
  2. Making use of or mutating "class variables" from within a method can cause all sorts of timing-dependency bugs. For example, it can cause situations where methodA must be executed before methodB can work.

Stampit doesn't have class inheritance, but prototypal inheritance is not just a form of inheritance, it is a superior form of inheritance. For more on these topics, see the blog post, Fluent JavaScript: Three Different Kinds of Prototypal OO, and the corresponding talk, Classical Inheritance is Obsolete: How to Think in Prototypal OO.

pluma commented 11 years ago

Thanks for your response.

I'm aware of your articles and talk. I did not mean to imply stampit had class inheritance, I was just noting that it did not have delegating inheritance, i.e. if you try to derive one stamp from another, the result will be a concatenation of both stamps' prototypes, so changes to either stamp will not be reflected on the "child" stamp.

For clarification: I am using the term "prototype" to refer to what is passed to Object.create, i.e. the resulting object's [[Prototype]] (or in your explanation, the "delegate prototype"). I'm not familiar with the term "exemplar prototype", but stamp.fixed.state is (if only in implementation) de facto equivalent what is called defaults in most JS libraries. Based on your response in the issue about the naming of enclose I understand why you decided against naming it something like initialize even if that seems descriptive (initializePrivateState would probably be even more correct, but a bit unwieldy).

I understand that the naming is intentional, but because it differs from the naming used by other JS libraries sharing the same or similar concepts, I don't think their naming clarifies their intent without sufficient background knowledge or an explanation like the one you provided.

Despite having read several articles on the issue, I'm still not entirely convinced that prototypical inheritance is necessarily superior to classical inheritance -- the lack of support for multiple delegate prototypes in JS certainly limits the usefulness of having delegate prototypes in the first place (especially in a library like stampit that wants to provide a mechanism for "type" composition -- I wrote a dummy that uses harmony direct proxies but I'm not happy with that either, nor with the simplified version that only deals with delegation).

ericelliott commented 11 years ago

I am aware of common convention in the JavaScript community. Unfortunately, a deep understanding of prototypal OO - even an understanding that there is more than one type of prototype - is uncommon. It is my hope that writing about it, speaking about it, and sharing this library will help change that.

Is there a learning curve? Sure. Do I think changing 'methods' to 'prototype' will make it easier to learn how to use Stampit effectively? No.

the lack of support for multiple delegate prototypes in JS certainly limits the usefulness of having delegate prototypes in the first place

There is no useful difference between having multiple delegate prototypes and concatenating multiple prototypes into one, as Stampit does when stamps are composed. Stampit supports inheriting from multiple prototypes as is.

Stampit does support inheriting from multiple delegates, keeping all of them as delegates. That is impossible with classical inheritance, as it is currently implemented in any JavaScrpit library or specification. So by your own criteria, prototypal methods are superior to classical, if only to satisfy your desired capability.

As for "other libraries sharing the same or similar concepts", I'm not concerned because there are none that are popular. The only popularly implemented inheritance libs implement concatenative (calling the method extend or mix in) OR classical.

Neither of those supplies a useful model for a complete understanding of prototypal inheritance, so it doesn't make sense to follow their conventions to describe different semantics.