Picture of Kev Adsett
Adapt OOP principles, and the 'mutate' function
by Kev Adsett - Tuesday, 1 October 2013, 11:00 AM
 

Hi all.

Something Fabien and myself have created in the current version of the framework is the existence of a 'mutate' function in the superclasses for views, models, collections and routers. This currently takes the following form:

mutate: function(proto){

var original = _.extend({}, this.prototype);

this.prototype._original = function(method, arguments) {

[... method validation and workarounds for ie8 ...]

original[method].apply(this, arguments);

}

_.extend(this.prototype, proto);

}

 

Using this function is quite simple.

For example, imagine you are creating a plugin which requires some changes to the ComponentView object, you could call ComponentView.mutate, and add in any extra functionality you need. You can even create a new version of an existing function, render, for example, and then call this._original("render").

 

This works very well when you are mutating an object once. However, once you try to mutate an already-mutated object, you run into the classic JavaScript inheritance problem where the scope of "this" doesn't change as you move up the inheritance chain. Meaning you end up in an infinite loop in the first mutated version of a function. Please bear with me on a fairly lengthy example:

Eg.

original:

original {

init: function() {

// initialise things

}

}

 

 

mutation1:

original.mutate({

init: function() {

// do some new stuff

this._original("init");

}

})

 

mutation2:

original.mutate({

init: function() {

// do some different new stuff

this._original("init");

}

})

 

If you were then to call original.init(), the order of execution would be:

  • mutation2's init get's called
  • this does "some different new stuff", then calls it's original (mutation1) init function
  • this does "some new stuff", then calls 'this._original("init")', but 'this' is still locked into the scope of the original caller, mutation2, meaning that this._original is referring to mutation1.
  • It then calls mutation1's init function infinitely.

Obviously this is going to break the minute that more than one plugin needs to mutate something, which I envisage will happen a lot, so we need a more robust way to be able to override methods but then call the original should we need to.

A rough brainstorm of solution ideas:

  1. Have some sort of mutations array in each of the core objects which stores the 'extended' versions of the functions, and calls all of those versions, as well as its own version, when the function is called.
    • What happens if you don't want to call the original?
    • What order do you call the functions
  2. Trigger an event every time a function is called using a standard naming convention, e.g. "[functionName]Called" and have any subclass of that object listen for events and call their own versions where appropriate.
    • What happens if you don't want to call the original?
    • What if you want to call the mutated function before the original?
    • I expect it'd get very messy but you could call two functions, one at the start and one at the end of each core function call, [functionName]Started and [functionName]Ended, or whatever.
  3. Pass two parameters into the "_original" function, the first as the name of the function, the second as the prototype for the original object to call it on... I haven't given that one much thought but there might be legs in it - it's difficult to see a situation where you wouldn't need "this".

It's a tricky one certainly. Any help, guidance or advice would be appreciated.

Kev

 

me
Re: Adapt OOP principles, and the 'mutate' function
by Sven Laux - Tuesday, 1 October 2013, 11:43 AM
 

Hi Kev,

just to help with the context, could you give an example of where this concept is being used in Adapt - i.e. is there a specific bit of functionality this lets us achieve or where we are thinking of using it? I could imagine that this is an area where documentation would help new developers to get up to speed...

Thanks, Sven

Picture of Kev Adsett
Re: Adapt OOP principles, and the 'mutate' function
by Kev Adsett - Tuesday, 1 October 2013, 12:14 PM
 

Hi Sven, good point thanks - maybe I'm getting ahead of myself with the technical bits.

So this situation applies when multiple plugins want to modify the same core object. For example, the triggered plugin modifies ('mutates') the ComponentView object, which is fine, up until the point when another plugin also wants to modify ComponentView. If they both want to change what happens when the 'render' function is called for example, then you will end up with an infinite loop as described above.

I hope that helps to clear things up.

 

Picture of Daryl Hedley
Re: Adapt OOP principles, and the 'mutate' function
by Daryl Hedley - Tuesday, 1 October 2013, 1:29 PM
 

Hey,

Could we throw into the mix another way of solving this. I really like the mutate function and can see a real use case but Javascript doesn't support super properly. Just looking around the web and finding discussions like this:

http://stackoverflow.com/questions/8032566/emulate-super-in-javascript

http://stackoverflow.com/questions/8765242/how-why-to-use-super-in-code

What you've posted is a common problem in Javascript and one that hasn't been properly solved. I also think with something that can modify a modified object - which is something Adapt is planning on doing with it's new plugin architecture. We might encounter a lot of issues. So another way around this is to not use something that javascript is extremely poor at and leave it up to the developer to implement their own way. Whether they want to use Backbone's way of calling super:

Backbone.Model.prototype.set.apply(this, arguments); - if there is only one level of modifications.

If there's anymore levels then the easiest way to solve this is to copy the code over. Although not the most ideal way - its certainly the easiest to implement and bug free.

Saying that - it would be good to get some prototypes and examples of what people are doing to solve this.

(this was an interesting read - http://jonathanfine.wordpress.com/2008/09/21/implementing-super-in-javascript/)

Thanks,

Daryl

Picture of Fabien O'Carroll
Re: Adapt OOP principles, and the 'mutate' function
by Fabien O'Carroll - Tuesday, 1 October 2013, 2:02 PM
 

One way of implementing super in javascript is by wrapping each method of an object in a closure, passing it the super function each time, this can be quite ugly but works pretty well. 

An example of that is below, excuse the messy code, it was the beginning of a side project i started. any questions just ask!

Thanks

Fabien

Picture of Daryl Hedley
Re: Adapt OOP principles, and the 'mutate' function
by Daryl Hedley - Tuesday, 1 October 2013, 2:54 PM
 

Hey,

I liked the simplicity of the mutate extend. Creating a system where there's a low barrier for entry especially for developers (as developers are what drive an open source project) something like wrapping every method of an object in a closure seems too messy. It's a good approach for something where there's not a lot of developers of varying ability contributing.

If we decide on something, it should be easy to implement and understand. Would be amazing if javascript could do something like this:

this.$super('render')

Thanks,

Daryl

Picture of Fabien O'Carroll
Re: Adapt OOP principles, and the 'mutate' function
by Fabien O'Carroll - Tuesday, 1 October 2013, 2:59 PM
 

We could only have something like that by wrapping it as shown above, the scope of `this` would be lost after a single super and result in a RangeError, the wrapping would be done within the core, so developers would not even need to know that this happening, we just give them the API that they can use.

n.b. thats not to say it won't be fully documented and explained, just that the implementation will not affect the barrier of entry

Picture of Daryl Hedley
Re: Adapt OOP principles, and the 'mutate' function
by Daryl Hedley - Tuesday, 1 October 2013, 3:39 PM
 

Hey,

Would be good to see a prototype of this working across multiple objects and how this would be implemented with something like Backbone. One problem that would need to be solved is when a developer creates a new plugin which then gets extended by another plugin - so something outside of core. Would be good to see how this can be implemented?

Thanks,

Daryl

Picture of Kev Adsett
Re: Adapt OOP principles, and the 'mutate' function
by Kev Adsett - Tuesday, 1 October 2013, 4:19 PM
 

I've been working on an assessment plugin today, in which it'd be great to be able to mutate QuestionView to trigger an event when the submit button is clicked on an assessment question.

However, because ComponentView is already mutated by a different component (triggered), and QuestionView extends ComponentView, I'm getting an infinite loop on initialize, even though I haven't even mutated initialize in QuestionView.

Triggered's mutated initalize is calling this._original('initialize'), with this as the scope of the mutated Question view, so it's _original is triggered's mutated ComponentView. Sad times.

What this tells us in practice, is that not only can you only mutate something once, but you then cannot mutate any of it's children either.

Picture of Rob Moore
Re: Adapt OOP principles, and the 'mutate' function
by Rob Moore - Tuesday, 1 October 2013, 6:39 PM
 

Hi

I was a little thrown by the mutate idea, but I remembered Backbone already contains the ability to extend Models indefinitely. Managed to find this example of extending the Model but it should be possible to apply the pattern to View as you can see they keep the parent constructor under _super_

https://gist.github.com/k33g/2287018

It's likely that the extend method is based on the jQuery extend which does a lot work under the hood to maintain the scope of `this`.

Sorry if this isn't quite right for you, I'm pretty knew to Backbone but can appreciate hanging onto the chain is pretty difficult in JS.

Cheers,
Rob

Picture of Kev Adsett
Re: Adapt OOP principles, and the 'mutate' function
by Kev Adsett - Wednesday, 2 October 2013, 8:26 AM
 

Hi Rob,

Thanks for your response. The challenge here is not extending models/views, but partially overriding them.

For example, in my previous post, I wanted to create some custom question functionality. You are right to point out that Backbone does contain the ability to extend models indefinitely, and I believe the same is true of Backbone views, collections and routers.

If I were to extend QuestionView with a new class MyQuestionView, and override the onSubmitClicked function. It would be easy to keep the correct scope when calling the super method, by doing: 

QuestionView.prototype.onSubmitClicked.apply(this)

The problem with this method is that, due to the need for this plugin architecture, I can't modify any of Adapt's core to use MyQuestionView rather than QuestionView. Furthermore, all of the other subclasses of QuestionView are going to remain subclasses of QuestionView, not MyQuestionView.

The mutate method was created with this in mind, that we actually want to override some bits of a core object but not all of it. Additionally we want to be able to refer to the original, non-modified version too. We also want all the subclasses of overridden object to inherit that mutation as well.

I hope that makes the problem a little clearer.

Picture of Kev Adsett
Re: Adapt OOP principles, and the 'mutate' function
by Kev Adsett - Thursday, 3 October 2013, 10:15 AM
 

Perhaps the best way around this is to go back to the old style of creating 'surrogate' objects that store a reference to the original object, and then extend the original by itself. That sounds confusing and ridiculous, but here's an example:

 

var SurrogateQuestionView = QuestionView;

QuestionView = QuestionView.extend({

init: function() {

SurrogateQuestionView.prototype.init.apply(this);

// custom init functionality

}

})

 

That way you get a snapshot of whatever QuestionView was at the time you extended, which means that you can extend it indefinitely in this fashion. 

The only drawback to this (other than the ugly prototype apply) is that I'm aware the scope of variables and the structure of Adapt as a whole in that regard is to change to be much more locked down, so that may have an implication here.

Picture of Chris Jones
Re: Adapt OOP principles, and the 'mutate' function
by Chris Jones - Thursday, 3 October 2013, 1:39 PM
 

It probably doesn't help much but Ember has a solid object model that has solved this.

Example here

Each method of a derived object has access to its own _super, including the init function.

I've been trying to convert this to Backbone, but not successfully yet.

 

Picture of Kev Adsett
Re: Adapt OOP principles, and the 'mutate' function
by Kev Adsett - Friday, 4 October 2013, 8:15 AM
 

Hi Chris, thanks for the example - I've had a play around and it does seem pretty solid. I'd be interested to see how you get on with the Backbone conversion.

Picture of Chris Jones
Re: Adapt OOP principles, and the 'mutate' function
by Chris Jones - Friday, 4 October 2013, 9:50 AM
 

Take a look at this one http://jsfiddle.net/7yneA/9/

I had to modify Backbone's extend method to keep a reference to the parent and introduce a create method instead of calling the new operator.

I won't claim to know Backbone inside out so I don't know if this would fundamentally break Backbone... the new operator should still work fine but you would loose the _super function.

Obviously this code is pretty spikey and just a proof of concept.

 

 

Picture of Fabien O'Carroll
Re: Adapt OOP principles, and the 'mutate' function
by Fabien O'Carroll - Friday, 4 October 2013, 1:12 PM
 

Kev and I worked on a version for Backbone this morning taking the principles i applied in class.js (zip file in post above).

We have a working copy that still works with new operator and will post an example shortly.

 

Picture of Fabien O'Carroll
Re: Adapt OOP principles, and the 'mutate' function
by Fabien O'Carroll - Friday, 4 October 2013, 3:10 PM
 

http://jsfiddle.net/7yneA/13/

Is the basic idea behind what we've done, let me know what you think Chris

Picture of Chris Jones
Re: Adapt OOP principles, and the 'mutate' function
by Chris Jones - Friday, 4 October 2013, 4:40 PM
 

Nice one, that's good stuff.

Can we package that as a separate library like "Backbone.Super"?

Just so we don't have to maintain our own fork of Backbone.

Picture of Kev Adsett
Re: Adapt OOP principles, and the 'mutate' function
by Kev Adsett - Monday, 7 October 2013, 8:14 AM
 

Hi Chris, 

We've not actually modified Backbone's core so no forking is required. At the moment, this exists in the AbstractView class (for now), which all Adapt views will (eventually) inherit from. I agree that taking it out and making a library of it's own that can sit as a layer between Backbone and Adapt is a good idea, and also means that we can hopefully reduce code re-use as the functions can be available to views, models, routers, and collections, without having to maintain the code in those four places.