Christoph Henkelmann
Christoph Henkelmann

Reputation: 1467

Is it possible to create a function with another prototype than Function.prototype?

I am working on a parser combinator library in JavaScript. For that I want to create functions that can be called like any other functions, but also have member functions that can be called in turn to produce output based on the function they are attached to (e.g. combinators).

I can of course add members to functions like this:

//the functions I want to add additional members to
function hello(x) {
    return "Hello " + x;
}

function goodbye(x) {
    return "Goodbye " + x;
}

//The function I want as a member of the above functions.
//it creates another function based on the function it is 
//attached to.
function double() { 
    var that = this;
    return function(x) {
        return that(x) + ", " + that(x);
    };
}

//I can attach them manually to the function objects:
hello.double = double;
//calling hello.double()('Joe') results in => "Hello Joe, Hello Joe"
goodbye.double = double;
//calling goodbye.double()('Joe') results in => "Goodbye Joe, Goodbye Joe"

I could create a function that augments all my functions with a double member, but I would have to remember to call it every time I create a Hey, Sayonara, etc. function. Also my greeting functions would have all those members each, directly on the function object, for each instance. I would prefer to put them all in one prototype and make this the prototype of all my greeting functions. The following options don't work either:

Is it even possible to give a function object a custom prototype or am I stuck with modifying each function object I create?


Update: I changed the above example to be more similar to the actual problem I am working on. This is about modyfing function objects not normal objects. The final goal is to enable a comfortable syntax for parser combinators, e.g. (much simplified):

//parses one argument
var arg = …
//parses one function name
var name = …
//parses a function call, e.g. foo(x+y, "test", a*2)
var functionCall = name.then(elem("(")).then(arg.repSep(",")).then(")").into(process(…))

I want to be able to add members to a set of functions so, when these members are called, they return new functions based on the function on which they were called. This is to be used for parser combinators / monadic parsers.

Upvotes: 5

Views: 893

Answers (4)

user663031
user663031

Reputation:

Yes, you can, using setPrototypeOf.

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf.

function prototype() { }
prototype.double = function() { console.log("double"); };

function myfunc() { }
Object.setPrototypeOf(myfunc, prototype);

myfunc.double();

This is essentially the standardized version of __proto__.

There is no other way to directly create a function with a prototype other than Function.prototype. Consider:

function makeFunc() {
    return function x() { };
}
function protofunc { }
protofunc.double = { }...
makeFunc.prototype = protofunc;
new makeFunc()

Unfortunately, by returning a function from the makeFunc constructor, rather than the default this, the makeFunc.prototype is not applied.

Or:

myFunc = Object.create(protofunc, ???);

Normally we think of using Object.create to create an object with a prototype, but in this case, there's no way to specify a function as being the object.

Bottom line: the only alternative is to set the prototype explicitly, which you can do either with __proto__, or setPrototypeOf. This is an ES6 feature. Normal browser support caveats apply. See http://kangax.github.io/compat-table/es6/.

Upvotes: 4

Georg Schölly
Georg Schölly

Reputation: 126135

A short answer to your question would be: Yes.

function Parent() {
    // constructor
}

function Child() {
    // constructor
}

Child.prototype = new Parent();

Here's how to add methods to your Child class:

Child.prototype.someMethod = function() {
    // do something
}

It's been a long time that I've used Javascript in an object-oriented fashion unfortunately, but I believe there are some solutions to make the syntax a little bit clearer.

Upvotes: -2

Brett Zamir
Brett Zamir

Reputation: 14355

Although I know you asked for a solution without using Function.prototype, I wonder whether you had considered that you can chain conditionally-executing behaviors such that a Function.prototype function like double() will allow a co-existence of different behaviors depending on "class" instance...

// This could also be adapted to itself be on Function.prototype
function chainConditions () {
    var args = arguments;
    return function () {
        for (var i=0, argl = args.length; i < argl; i++) {
            var ret = args[i].apply(this, arguments);
            if (typeof (ret) !== 'undefined') { // If you want to allow undefined to be returnable, you could instead add this to a try-catch and ignore those which throw exceptions or a certain exception
                return ret;
            }
        }
    };
}
Function.prototype.double = function () {
    if (this.prototype instanceof Salutation) {
        var that = this;
        return function(x) {
            return that(x) + ", " + that(x);
        };
    }
};
Function.prototype.double = chainConditions(
    function () {
        if (this.prototype instanceof Tennis) {
            var that = this;
            return function(x) {
                return that(x) + ", " + that(x);
            };
        }
    },
    Function.prototype.double
);


function Salutation () {}

function Hello(x) {
    return "Hello " + x;
}

function Goodbye(x) {
    return "Goodbye " + x;
}
Goodbye.prototype = new Salutation();


function Tennis () {
}
function TennisPlayer (x) {   
    return x + ' serve';
}
TennisPlayer.prototype = new Tennis();



alert(TennisPlayer.double()('Forehand')); // Forehand serve, Forehand serve

alert(Goodbye.double()('Yellow Brick Road')); // Goodbye Yellow Brick Road, Goodbye Yellow Brick Road

alert(Hello.double()('Yellow Brick Road')); // does not work as we did not use Hello.prototype = new Salutation();

One should be able to abstract this even further by allowing one function to be used for the class type checking (e.g., grouping all Salutation methods on the Function.prototype together as one type check), and another to accept a list of methods keyed to their conditional behaviors.

While it uses its own API, it is fairly non-obtrusive and can even be made to work with existing methods on the Function.prototype which do not play along by throwing an exception (just be sure to include any pre-existing methods last).

Upvotes: 1

selbie
selbie

Reputation: 104559

A couple of different options. I think you should be thinking in terms of objects and inheritance instead of functions and globals. In the example above, you are basically implying you want "world" to inherit the set of hello methods. (Or do you want "hello" to inherit "world"?)

You should start by reading this paper here: http://www.crockford.com/javascript/inheritance.html

Here's some simple code that demonstrates inheritance using "prototypal inheritance"

// ---------------
function inherit (obj, base) {
    var tmp = function () { };
    tmp.prototype = base.prototype;
    obj.prototype = new tmp();
    obj.prototype.constructor = obj;
};

function HelloSet() {
    ;
}

HelloSet.prototype.helloA = function() {
    return "Hello A " + this.displayName;
};

HelloSet.prototype.helloB = function() {
    return "Hello B " + this.displayName;
};

function World(displayName) {
    HelloSet.constructor.call(this); // constructor chaining
    this.displayName = displayName;
}

inherit(World, HelloSet);  // World now has all the methdods of HelloSet
// ---------------

// now let's create some worlds
var earth = new World("Earth");
var mars = new World("Mars");

earth.helloA();  // returns "Hello A Earth"
mars.helloB();  // returns "Hello B Mars"

Upvotes: 0

Related Questions