Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #4060 (closed defect: untested)

Opened 2 years ago

Last modified 1 week ago

[PATCH] Flexible inheritance, method privacy, other OO additions

Reported by: anonymous Assigned to: sam@conio.net
Priority: high Milestone: 1.2
Component: Prototype Version: 1.1.1
Severity: normal Keywords: Class create super inheritance privacy extend
Cc: newmanb@stanford.edu, brenocon@gmail.com, aljoscha@weisshuhn.de, petermichaux@gmail.com, mislav

Description

Greetings, all,

I'm submitting this modification of Class.create after five months of rigorous and joyful use. The Prototype library is devoted to JavaScript OO, and I think this addition marks a significant step in that direction. It's a little dense in the implementation, but I make the following claims about it:

  • Completely backwards-compatible with the rest of the library.
  • Class creation syntax is significantly cleaner. See http://groupspace.org/devben/proto-svn/demo/
  • Because Class.create and Object.extend no longer need to be separated, the way is paved for automatic documentation that really understands what's going on.
  • Easy access to overridden methods via this.sup. Nested this.sup calls work properly, and all opportunities for infinite recursion have been dealt with.
  • Any JavaScript object can be used as a superclass.
  • Inherited method calls are resolved by dynamic lookup (prototype chain), rather than by aggregating all inherited methods into a single object. This means subclassing is a constant-time operation.
  • Method privacy can actually be enforced (or not). Pick your own naming convention!

Again, see this demo: http://groupspace.org/devben/proto-svn/demo/. There you'll also find a link to unit tests. A commented version of the code (with a few stray features I decided to cut, like support for interfaces) can be found here: http://groupspace.org/devben/proto-svn/src/base-dev.js.

I don't expect anybody to fall in love with this at first sight, but I'm endlessly willing to chat about it.

Best, Ben (newmanb@stanford.edu)

Index: src/base.js
===================================================================
--- src/base.js	(revision 3751)
+++ src/base.js	(working copy)
@@ -1,11 +1,188 @@
 var Class = {
-  create: function() {
-    return function() { 
-      this.initialize.apply(this, arguments);
+
+  // You'll definitely want to check out
+  // http://groupspace.org/devben/proto-svn/demo/
+  // to get a mile-high understanding of why I
+  // bothered with all of this.
+
+  NAME: "Class",
+  VERSION: Prototype.Version || 2.0,
+  // put your name here if you've helped in any way!
+  AUTHORS: "Ben Newman (of groupspace.org), et al.",
+  
+  __repr__: function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+  },
+
+  toString: function() {
+    return this.__repr__();
+  },
+
+  _doNotInit: {},
+
+  create: function(declarations) {
+
+    var ctor = function() {
+      if (arguments[0] !== Class._doNotInit && this.initialize)
+        this.initialize.apply(this, arguments);
+    };
+
+    if (declarations) {
+      var ownPrototype = function() {
+        for (var i in declarations)
+          Class._attachClassProperty(i, declarations[i], this);
+        Class._ensureDefaults(this, declarations);
+      };
+
+      if (declarations.extending) {
+        var parentPrototype = Class._inherit(ownPrototype, declarations.extending);
+        declarations['sup'] = Class._makeSup(parentPrototype);
+      }
+
+      Class._inherit(ctor, ownPrototype);
     }
+
+    return ctor;
+  },
+
+  _attachClassProperty: function(propName, prop, ownPrototype) {
+    ownPrototype[propName] = prop;
+    if (typeof(prop) == 'function')
+      Class._addStackLogic(ownPrototype, propName);
+  },
+
+  _setUpAndHideCallStack: function() {
+
+    var __callStack = []; // stores [function-name, receiver] pairs
+
+    Class._lastOnCallStack = function() {
+      if (__callStack.length == 0) return ['', null];
+      else return __callStack[__callStack.length - 1];
+    };
+    
+    Class._addStackLogic = function(obj, fnName) {
+      switch (fnName) {
+        case 'sup':
+        case 'toString':
+        case 'valueOf': return;
+        default:
+        var __func = obj[fnName];
+        obj[fnName] = function() {
+          var lastRef = Class._refOfLastCallingObject();
+          if (Class.followsPrivateNamingConvention(fnName) &&
+              (lastRef || {}).constructor !== this.constructor) {
+            Class.privateCallError(fnName);
+          } else {
+            __callStack.push([fnName, this]);
+            var result = __func.apply(this, arguments);
+            __callStack.pop();
+            return result;
+          }
+        };
+      }
+    };
+    
+    Class._grant = function(obj) {
+      var __func = this;
+      var __callStackMemory = Class._lastOnCallStack();
+      return function() {
+        __callStack.push(__callStackMemory);
+        var result = __func.apply(obj || this, arguments);
+        __callStack.pop();
+        return result;
+      }
+    }
+
+    // this function commits seppuku
+    Class._setUpAndHideCallStack = undefined;
+    
+  }, // ends: _setUpAndHideCallStack
+
+  _nameOfLastCalledMethod: function() {
+    return Class._lastOnCallStack()[0];
+  },
+
+  _refOfLastCallingObject: function() {
+    return Class._lastOnCallStack()[1];
+  },
+
+  // change this at will
+  followsPrivateNamingConvention: function(propName) {
+    return propName.charAt(0) == '_';
+  },
+  
+  privateCallError: function(fnName) {
+    alert("Error: Tried to access private member " + fnName +
+          " from from non-member method " +
+          Class._nameOfLastCalledMethod());
+  },
+
+  _ensureDefaults: function(ownPrototype, declarations) {
+    ['toString',
+     'toLocaleString',
+     'valueOf'].each(function(prop) {
+       if (declarations[prop]) {
+         Class._attachClassProperty(prop, declarations[prop], ownPrototype);
+       }
+     });
+
+    // see whether property was implemented by *this* class
+    ownPrototype.ownPrototypeHas = function(property) {
+      return (typeof(declarations[property]) != 'undefined');
+    }
+  },
+
+  _makeSup: function(parentPrototype) {
+    Class._skipOver = false;
+    
+    return function() {
+      var origin = Class._nameOfLastCalledMethod();
+      
+      if (!Class._skipOver && !this.ownPrototypeHas(origin) &&
+          parentPrototype.sup && !parentPrototype.ownPrototypeHas(origin)) {
+        return parentPrototype.sup.apply(this, arguments);
+      }
+      
+      if (parentPrototype.sup && !Class._skipOver) {
+        Class._skipOver = true; // skip this test next time
+        return parentPrototype.sup.apply(this, arguments);
+      }
+
+      Class._skipOver = false;
+      
+      var args = $A(arguments);
+      with (this.sup = parentPrototype.sup || this.sup)
+        return parentPrototype[origin].apply(this, args);
+    };
+  },
+
+  encap: function(__val, mutable) {
+    if (!mutable) return function() { return __val; }
+    else return function(/* optional new value */) {
+      if (arguments.length > 0) __val = arguments[0];
+      return __val;
+    }
+  },
+
+  // superclass can be a constructor function or an existing object
+  _inherit: function(subclass, superclass) {
+    if (typeof(superclass) == 'function') {
+      superclass.getInstance = superclass.getInstance ||
+        Class.encap(new superclass(Class._doNotInit));
+      subclass.prototype = superclass.getInstance();
+    } else if (typeof(superclass) == 'object')
+      subclass.prototype = superclass;
+    return subclass.prototype;
   }
+
 }
 
+// Initialize and forever hide the call stack:
+Class._setUpAndHideCallStack();
+
+Function.prototype.grant = Class._grant;
+var createClass = Class.create;
+
 var Abstract = new Object();
 
 Object.extend = function(destination, source) {

Attachments

patchClass.diff (5.7 kB) - added by newmanb@stanford.edu on 03/03/06 08:22:24.
Same diff as an attachment, in case that suits you better.
base.js.diff (8.0 kB) - added by newmanb@stanford.edu on 03/28/06 11:00:49.
An update. More than just an iteration, too. See my upcoming comments for a summary.
base.js.2.diff (10.0 kB) - added by newmanb@stanford.edu on 05/18/06 17:03:19.
Important bugfix due to Dominic Clifton. Exceptions can no longer corrupt the call stack. Added to unit tests.
base.js.3.diff (6.9 kB) - added by newmanb@stanford.edu on 05/27/06 04:14:45.
A slimmer version of the patch. This does not support method privacy restrictions, but I haven't found anyone who really loved them, and there was a slight performance hit... Also, Dean Edwards won me over to the .extend() syntax for subclassing, and it was easy to implement (four lines), so that's supported now too.
base.js.4.diff (5.4 kB) - added by newmanb@stanford.edu on 05/27/06 11:17:32.
Yet further progress. Since I'm no longer trying to support method privacy, it turns out the call stack isn't needed at all. This insight came to me while reading Dean Edwards' Base.js. Unit tests of the slimmer framework here: http://groupspace.org/devben/proto-minimal/test/unit/class.html
base.js.5.diff (5.5 kB) - added by newmanb@stanford.edu on 05/30/06 14:02:01.
Great news. The size of the patch just dropped again, and I've actually added the method privacy feature back in! New simplifications are just leaping out at me. For those scoring at home, this still is the only proposed inheritance system that pretends to be backwards compatible with the current version of Prototype. It trivially supports the extend() syntax which makes Dean Edwards' system attractive, it has seen more active use than Base.js, and, frankly, the code is easier to read (i.e. more extensible & maintainable). These might sound like bold claims, but I'm prepared to stand behind them. The call stack dinosaur is long gone. What's left is growing harder and harder to dislike. If you're reading this, Sam, you might have more of a decision to make than you thought.

Change History

03/03/06 08:22:24 changed by newmanb@stanford.edu

  • attachment patchClass.diff added.

Same diff as an attachment, in case that suits you better.

03/03/06 08:51:44 changed by brenocon@gmail.com

  • summary changed from [PATCH] An in-depth experiment with Class.create to [PATCH] Flexible inheritance, method privacy, other OO additions.

Ben was a little too humble :) and neglected to post the link to the paper he wrote on this, that explains in detail the design and implementation of dynamic lookup for inheritance -- you can now access your superclass's overriden methods and more. It's a big change, it needs a big description. Lots of examples inside.

http://www.stanford.edu/~newmanb/cs242_paper_deme.pdf

I'm also changing the ticket title to be more descriptive.

03/03/06 12:40:32 changed by Martin Bialasinski

Looks really nice :-) Proper inheritance is really missing in the current Class implementation.

How about benchmarks? How does the added call stack affect execution speed?

Despite the comment on attachMethod(), I really like Javascript's flexibility to dynamically change the prototype. Therefore, a Class.detachMethod() would be appreciated.

Is it like

delete TheClass.prototype.methodName
delete TheClass.prototype._runTimeDeclarations[methodName]

or is there more to it?

Maybe also Class.extend(className, objectWithPropertyPairs) and Class.remove(className, arrayWithPropertyNames) convenience methods. Basically iterations, so trivial to add, I believe.

03/03/06 20:02:12 changed by newmanb@stanford.edu

@Martin: You're exactly right about the detachMethod implementation, and I like the idea of using the familiar XXX.extend method name (along with Class.remove) for the second idea. Given the way interfaces are implemented in my base-dev.js file, they could easily be used for that purpose (they're not like Java interfaces, since you can actually provide default code if you want). I should probably implement those operations in terms of attachMethod and detachMethod, too.

And you've also hit the nail on the head with the request for benchmarks. I'm not hiding anything there -- I just haven't done the tests. I'd be glad to look into it and report the results. In case they are ugly, two things to say:

  1. My Class.create scheme is opt-in; if you don't need this.sup, you can program however you like. It would also be easy to allow declarations that disabled this.sup and method privacy on a per-class (or per-method) basis for the sake of speed.
  2. If we don't care about Opera, arguments.callee.caller provides a cleaner solution for this.sup, and privacy checks could be turned off for production code (they're a development tool, really), so the callstack could be avoided altogether.

But I suspect the benchmarks will be positive.

Ben

03/07/06 06:33:27 changed by anonymous

  • cc changed from newmanb@stanford.edu to newmanb@stanford.edu, brenocon@gmail.com.

03/20/06 10:45:35 changed by anonymous

  • cc changed from newmanb@stanford.edu, brenocon@gmail.com to newmanb@stanford.edu, brenocon@gmail.com, aljoscha@weisshuhn.de.

03/27/06 21:25:58 changed by anonymous

  • priority changed from low to highest.

03/28/06 11:00:49 changed by newmanb@stanford.edu

  • attachment base.js.diff added.

An update. More than just an iteration, too. See my upcoming comments for a summary.

03/28/06 11:48:40 changed by newmanb@stanford.edu

Good news. The quarter has ended and I've had a chance to dive back into my enhancements of the Class object (defined in base.js). A new version of the patch, relative to the latest version in the repository, can be found in base.js.diff (attached above).

I've done some performance tests, and the results are good, though it is difficult to obtain reliable numbers using a language that is garbage-collected and is being interpreted in a browser. Nevertheless, it should gratify you to know that, in general (say 95% of the time), class methods take no more than twice as long to execute as simple functions. In fact, the difference often gets lost in the noise, and when the function body is substantial, it becomes quite negligible. So I guess the same advice you hear in the Python world holds: don't litter your classes with tiny methods.

Or not! Here's why that advice doesn't really matter. To appease the performance-minded, I have added a bit of syntax for marking "critical" functions:

var MyClass = {

  initialize: function() {
    this.count = 100000;
  },

  critical: {
    foo: function(elt) {
      var whatever = elt;
      return whatever;
    }
  },

  bar: function(elt) {
    for (var i = 0; i < this.count; ++i)
      this.foo(i);
  }

};

In the code above, the "foo" function has been designated as a function that should be called without updating the callstack or checking privacy. This means it will be denied the ability to call this.sup and won't be influenced by privacy restrictions, but it will be called directly, as fast as possible. Any inner-loop function (e.g. the callback for a mousedragged event) might be a good candidate for being marked "critical."

But that's not all. I have revamped the code for the Class object so that the object itself is no longer defined as a hash of functions but is instead defined as a big constructor function and then instantiated using "new." These two approaches have essentially the same end result, but the second way makes it very easy to keep private fields hidden and expose only those functions that need to be exposed. Also, the implementation of makeSup should be considerably easier to understand, as I've replaced its ugly recursion and abuse of static scoping with a simple while-loop.

Since I got positive feedback about the attachMethod and deleteMethod functions, they are now fully implemented and covered by my unit tests.

An updated demo, with link to updated unit tests: http://groupspace.org/devben/proto-svn/demo/

Still a fair amount of code to digest, but if you play around with the demo I think you'll get the hang of the functionality. That is, of course, the whole point: I'd like to save you from bothering with uninteresting details as much as I can. If you do take a look at the updated code, however, your questions and/or advice would be most welcome.

Regards, Ben

03/28/06 11:59:26 changed by newmanb@stanford.edu

In the example, the first line should be "var MyClass = Class.create({", and the very last line should be "});". Whoops.

04/20/06 03:01:33 changed by khurram.mahmood@workday.com

  • priority changed from highest to high.
  • type changed from enhancement to defect.

mate, this is an awesome library, makes OO much more convenient and natural in javascript.

However, I am running into an issue with multiple levels of inheritance. Is this even supported?

Try the following piece of code to replicate the problem:

var A = Class.create({

initialize: function(say) {

alert(say);

}

});

var B = Class.create({

extending: A, initialize: function() {

this.sup('hello');

}

});

var C = Class.create({

extending: B, initialize: function() {

this.sup();

}

});

var cObj = new C();

I expected that C's initialize would call B's which would in turn pass 'hello' as an argument to A's initialize but instead the create code looks directly calls A's initialize completely by-passing B.

Is that a bug or an unsupported feature, without this the library is unusable for me.

Thanks very much for your help.

Khurram

04/20/06 04:59:05 changed by anonymous

Khurram,

I totally agree with you about the importance of that feature. In my browser(s), it seems to work the way you (and I) want it to. Can you run the unit tests and let me know if they're failing in your browser? Here's the link:

http://groupspace.org/devben/proto-svn/test/unit/class.html

Since your code is really important, I've made it one of the examples in the demo (see http://groupspace.org/devben/proto-svn/demo/, and click on "Longer inheritance chain"). I just copied/pasted your code, and it seemed to do the right thing (popped up the alert saying "hello").

I don't want to ignore any modern browsers or platforms, so if you've found a compatibility problem, I'm excited to see what's up.

Thanks, Ben

04/20/06 17:12:47 changed by khurram.mahmood@workday.com

Thanks Ben for such a prompt response. Your demo and the unit tests work fine in my browser but the local file I have still gives the problem in both firefox and IE6. Perhaps I have the wrong version of the library. I downloaded my version from: http://groupspace.org/devben/prototype/dist/prototype.js

It has your Class.create code in it but is slightly different from the one you pasted above. Is this the right version, if not how do I get the correct verion?

Thanks very much,

Khurram

04/20/06 17:22:00 changed by anonymous

The file to use is http://groupspace.org/devben/proto-svn/dist/prototype.js. The one you grabbed is somewhat out of date (it's from the old darcs repository).

If you grab your own copy of the library from the subversion repository, you'll want to replace the file src/base.js with mine: http://groupspace.org/devben/proto-svn/src/base.js. Then just do 'rake dist' to build everything. This might be the better route if you don't want any of my other various changes to the rest of the library.

Ben

04/20/06 18:13:25 changed by khurram.mahmood@workday.com

Looks good mate. Thanks.

I like what you have done, we are going to use it all over the place. Thanks for making my life easier :-)

04/20/06 21:24:02 changed by frank.law@workday.com

Fantastic library, really like what you have done. Did have one question about overriding a method and then calling the overridden method of the super. Can this be done? Here is the syntax I was trying and it is throwing an error that the this.sup.foo() method doesn't exist. I also tried it as this.sup().foo(), and still got an error. Actually while I was writing this, one of the guys here pointed out the getBaseInstance() method, is that what was intended to do what I am trying to do?

Here is the code, I appreciate any suggestions you have. Thanks, Frank

var Base = Class.create({
	initialize: function(){

	},
	
	foo: function() {
		alert('foo');
	}
});

var ExtendBase = Class.create({
	extending: Base,
	
	initialize: function() {
		this.sup();
	},
	
	
        foo: function() {
		this.sup.foo();
		alert('foo2');
	}

});


function testOverride() {

	var oBase = new ExtendBase();
	oBase.foo();
}

04/20/06 21:42:45 changed by frank.law@workday.com

Just an addition, I did manage to get it to work by using the following, but was wondering if there was another intended way...

this.getBaseInstance().foo.apply(this, [])

04/20/06 22:15:37 changed by anonymous

Hey Frank,

You bring up an important point. this.sup is a method, and it always represents the superclass's version of the method in which it (this.sup) is being called. This is a bit different from Java's 'super' keyword, but I find the current syntax of this.sup pretty useful.

The basic idea is this. As you noted in your follow-up email, you can always grab the superclass explicitly (if you know what it is), look at its prototype, and use 'apply' to call the overridden method. In your code, that might mean

    foo: function() {
        Base.prototype.foo.apply(this, []);
        alert('foo2')
    }

Using getBaseInstance() is a little risky, since the method might have been defined even further up the inheritance chain, and then you wouldn't really be getting a different version of the method by looking inside the direct superclass. The syntax used above does work in all circumstances, but it's ugly.

You should think of this.sup as a perfect substitute for that ugly syntax. If you wrote this code instead, all the ugliness (and none of the functionality) would go away:

    foo: function() {
        this.sup();
        alert('foo2')
    }

In the context of the foo method, calling this.sup() is synonymous with writing 'Base.prototype.foo.apply(this, []);'. This demonstrates that the behavior of this.sup depends on where you call it, which is admittedly unusual but saves a lot of typing.

Java's 'super' keyword behaves like an instance of the superclass, allowing you to access any of its fields using the dot notation. When I examined my own use patterns, however, I found that whenever I needed to refer to a superclass, it was almost always because I was calling an overridden version of the current method. It was a pain to give an explicit field name when it was always just going to be the same as the current method's name, so I decided to let this.sup just *be* the method I knew I wanted. In terms of code, this means writing just 'this.sup(arg)' instead of 'this.sup.methodname(arg)'.

(Another motivation was that, in JavaScript, it's a lot easier to make a function behave a certain way in a given context than it is to make the fields of an object change according to the context.)

For the minority use-case in which you really want to call a different method in the superclass, you've still got the Base.prototype.otherMethod.apply(this, ...) syntax.

I hope that helps. Do you like the syntax, or do you think it's too surprising?

Thanks for the input, Ben

04/20/06 22:56:33 changed by frank.law@workday.com

Ben,

Thanks for the info, I am fine with that syntax, it makes sense. Thanks for the information and all of the hardwork!

-Frank

05/18/06 17:03:19 changed by newmanb@stanford.edu

  • attachment base.js.2.diff added.

Important bugfix due to Dominic Clifton. Exceptions can no longer corrupt the call stack. Added to unit tests.

05/18/06 17:07:32 changed by newmanb@stanford.edu

FYI, http://groupspace.org/devben/proto-clean/dist/prototype.js is an up-to-date version of the whole library, with my Class.create modifications applied.

05/27/06 04:14:45 changed by newmanb@stanford.edu

  • attachment base.js.3.diff added.

A slimmer version of the patch. This does not support method privacy restrictions, but I haven't found anyone who really loved them, and there was a slight performance hit... Also, Dean Edwards won me over to the .extend() syntax for subclassing, and it was easy to implement (four lines), so that's supported now too.

05/27/06 04:48:42 changed by newmanb@stanford.edu

Hey, Sam,

This is a first response to your concerns about the size of the patch. I'm not trying to be all things to all people; honestly, I'm just trying to help improve Prototype. Dean and I have shared some advice, so I hope what you finally decide to use reflects a collaboration between us and you and everybody else who cares.

I've got 5 of Dean's 6 requirements covered. Adding support for static methods never seemed all that useful, since you can just set ordinary properties on the constructor function itself. Object.extend was made for this! (That's all Dean does, by the way -- he doesn't allow inheritance for static methods, and I have no idea how he could, since his inheritance mechanism depends on this.base.)

I've taken a slightly different approach to the internal representation of classes. I'm making full use of prototype chains and dynamic lookup, whereas Dean (and the current Prototype library) just copy all inheritable methods into the derived class. In my system, subclassing is constant time, and the memory requirements are much smaller. Your strategy (like Dean's) makes dynamic lookup a bit quicker, but I suspect the difference is nominal. I will grant that my strategy requires a more complex implementation. I'll hear any suggestions about simplifying it (but this latest patch should help!).

Regards, Ben

P.S. The 'mimic' function that you might notice in the demo (http://groupspace.org/devben/proto-minimal/demo/ or http://groupspace.org/devben/proto-svn/demo/) is still experimental, if only because Safari doesn't allow DOM objects as prototypes (i.e. you can't stick the constructed object into the DOM).

05/27/06 09:16:43 changed by newmanb@stanford.edu

@myself - You misrepresented Dean's strategy; he uses prototype chaining too. Way to read, champ.

Ben

05/27/06 11:17:32 changed by newmanb@stanford.edu

  • attachment base.js.4.diff added.

Yet further progress. Since I'm no longer trying to support method privacy, it turns out the call stack isn't needed at all. This insight came to me while reading Dean Edwards' Base.js. Unit tests of the slimmer framework here: http://groupspace.org/devben/proto-minimal/test/unit/class.html

05/30/06 14:02:01 changed by newmanb@stanford.edu

  • attachment base.js.5.diff added.

Great news. The size of the patch just dropped again, and I've actually added the method privacy feature back in! New simplifications are just leaping out at me. For those scoring at home, this still is the only proposed inheritance system that pretends to be backwards compatible with the current version of Prototype. It trivially supports the extend() syntax which makes Dean Edwards' system attractive, it has seen more active use than Base.js, and, frankly, the code is easier to read (i.e. more extensible & maintainable). These might sound like bold claims, but I'm prepared to stand behind them. The call stack dinosaur is long gone. What's left is growing harder and harder to dislike. If you're reading this, Sam, you might have more of a decision to make than you thought.

05/30/06 14:12:06 changed by newmanb@stanford.edu

Well, okay, I lied about the size. I was using lines of code as a rough estimate, but the size actually increased by 0.1k. That's including the privacy feature, though, which may or may not stick around. The important point is that the patch is now much, much simpler, with no loss in functionality.

Head over to http://groupspace.org/devben/proto-minimal/demo/ to see for yourself!

05/30/06 14:27:31 changed by aljoscha@weisshuhn.de

Your approach shows thoughtfulness but Dean Edwards Base.js (see http://dean.edwards.name/weblog/2006/05/prototype-and-base/ ) fits much more naturally into the prototype code. It is more expressive and lean.

05/30/06 15:29:14 changed by newmanb@stanford.edu

@aljoscha - Have you had a look at my patch recently? If you're going by the explanation of my approach that I gave a few days ago, then you're falling into a misconception that I fell into myself. I have realized that some substantial components of that approach are no longer necessary, so my apology about the complexity no longer stands. This patch is getting simpler and simpler (and smaller, but at 5k, that's not a huge concern). Please don't cling to the impressions you may have formed in the more distant past.

Dean's expressive syntax is relatively easy to support; in fact, I've already added the ClassName.extend(withTheseProperties) syntax to my system, because I think convergence is a good thing. You claim his code fits more naturally into Prototype. I don't know what that means, exactly, but I stand by the promise that Class.create should continue to work as it always has, whereas he has introduced a new global object. If my system is not fitting "naturally into the prototype code" while his is, then I am confused.

Dean's code is lean at the expense of being somewhat difficult to read. I am not one to talk, of course; this patch used to be pretty ugly, but the current version is rich with clear variable and function names. You're a programmer. I trust you to draw your own conclusions. Thanks for hearing my position.

Best, Ben

06/02/06 08:14:39 changed by anonymous

The patch base5.diff is wrong, there is two closing brackets after the ensurePrivacy block and there should be only one (as it is in the code that's running on your unit tests. Good work by the way :-)

06/02/06 08:32:46 changed by newmanb@stanford.edu

I actually puzzled over that for a while, too. It seems out of place, but I think it's correct. The extra } has a - in front of it, so I think it must be one of the curly braces that was originally part of the previous version of Class.create(). Why svn diff chose to put the -} there instead of at the end of the diff, I have no idea.

Ben

06/24/06 14:23:58 changed by anonymous

here is another similar but smaller patch: [5459]

06/24/06 14:38:41 changed by newmanb@stanford.edu

Doesn't exist yet?

(I'm excited to see this, but similarity gives a wide margin for variations in size, so the details are kind of essential.)

07/24/06 06:14:07 changed by petermichaux@gmail.com

  • cc changed from newmanb@stanford.edu, brenocon@gmail.com, aljoscha@weisshuhn.de to newmanb@stanford.edu, brenocon@gmail.com, aljoscha@weisshuhn.de, petermichaux@gmail.com.
  • keywords changed from Class.create super inheritance privacy to Class.create super inheritance privacy extend.
  • version changed from 1.0.0 to 1.1.1.
  • milestone changed from 1.x to 1.2.

This patch really is very big. There is a much simpler way to get class-based inheritance in JavaScript that duplicates the method inheritance and chains the constructors just like Ruby itself. And it only requires eight lines of JavaScript.

Please see

http://www.kevlindev.com/tutorials/javascript/inheritance/index.htm

And my blog comparing this system with Ruby to see how similar they become.

http://peter.michaux.ca/articles/2006/06/02/class-based-inheritance-in-javascript

Notice there is no need for methods called "initialize" because the JavaScript constructors are used for their actual intent: constructing objects.

Please email me if you have any questions about this method. If Prototype.js is to have class-based inheritance I hope it is the method described in these two links.

07/24/06 10:17:50 changed by petermichaux@gmail.com

  • keywords changed from Class.create super inheritance privacy extend to Class create super inheritance privacy extend.

Here is another example of how great the simple eight line extend function is and how it makes JavaScript work like Ruby but in a natural JavaScript way. This example shows how adding functions to the superclass are automatically available in the subclass. This is just like Ruby's open class feature.

http://peter.michaux.ca/articles/2006/07/24/ruby-open-classes-and-inheritance-in-javascript

07/24/06 17:20:30 changed by petermichaux@gmail.com

Here is another example of how the simple extend function allows for simulation of Module#remove_method in JavaScript. This is due to proper prototype chaining instead of copying functions from the superclass to the subclass.

http://peter.michaux.ca/articles/2006/07/24/ruby-module-remove-method-in-javascript

07/25/06 02:17:59 changed by petermichaux@gmail.com

Here is another example of how the simple extend function allows for simulation of Module#remove_method in JavaScript. This is due to proper prototype chaining instead of copying functions from the superclass to the subclass.

http://peter.michaux.ca/articles/2006/07/24/ruby-module-remove-method-in-javascript

07/25/06 02:18:53 changed by anonymous

  • priority changed from high to highest.

(follow-up: ↓ 35 ) 08/24/06 02:45:27 changed by newmanb@stanford.edu

This patch is no longer worth salvaging as-is. Kevin Lindsey's eight-line extend method is intriguing but not really comparable in purpose. I've recently rewritten the patch from scratch, and the latest version comes to just 48 lines, or 1.3k. I'm still a little stunned with the results!

Since this bug tracker is best for small, uncontroversial fixes, not research projects, a complete rationale can be found here: http://seraph.im

The Prototype library with the new patch can be found here: http://seraph.im/prototype/dist/prototype.js

Unit tests on the way. Be patient with my site: it's a TiddlyWiki. I'm planning to incorporate a backend sometime soon...

08/30/06 07:58:29 changed by petermichaux@gmail.com

Although it is possible to simulate private methods in JavaScript I don't think it is necessary or should even be encouraged. If a method is intended to be private then using the _underscoreNamingConvention is sufficient. Give developers the freedom to shoot themselves in the foot if they want to start hacking with these methods.

There are a few reasons why simulated private methods aren't needed or worth the bloat. First, JavaScript has to be downloaded so simulating private methods causes bigger downloads. Second, although private methods are nice in OOP languages, JavaScript doesn't run into the hundreds of thousands of lines with many developers that absolutely _necessitate_ the safety of private methods. Third, when pinched, a developer sometimes wants to hack and use the simulated private methods. Fourth, the code needed to simulate private methods requires maintainence and will have bugs or unexpected behavior developers must learn about. All these problems when the _underscoreNamingConvention and a little discipline are sufficient.

(in reply to: ↑ 33 ) 09/04/06 22:37:12 changed by madrobby

  • status changed from new to closed.
  • resolution set to untested.

Replying to newmanb@stanford.edu:

You're probably right on playing with this around off-trac. Maybe this could be nice add-on for people who want this and be made in Prototype later (but that really depends on Sam's take on this).

10/02/06 21:46:51 changed by mislav

  • cc changed from newmanb@stanford.edu, brenocon@gmail.com, aljoscha@weisshuhn.de, petermichaux@gmail.com to newmanb@stanford.edu, brenocon@gmail.com, aljoscha@weisshuhn.de, petermichaux@gmail.com, mislav.