12

How can I make constructor for callable object in JavaScript?

I've attempted various ways, like following. The example there is just shortened example of actual object.

function CallablePoint(x, y) {
    function point() {
        // Complex calculations at this point
        return point
    }
    point.x = x
    point.y = y
    return point
}

This works at first, but the object it creates isn't instance of CallablePoint, so it doesn't copy properties from CallablePoint.prototype and says false on instanceof CallablePoint. Is it possible to make working constructor for callable object?

Konrad Borowski
  • 11,584
  • 3
  • 57
  • 71
  • 2
    You know that `function point() { return point }` would just return the function itself? What do you intend with this? It would allow you to do `p()()()()()()()()()` but what is the point? (no pun intended). – Felix Kling Sep 29 '12 at 20:16
  • 3
    @FelixKling understandably I had to recuse myself from answering this question – Pointy Sep 29 '12 at 21:06
  • For the sake of completeness, ES6 has better solution to this, see: http://stackoverflow.com/questions/36871299/how-to-extend-function-with-es6-classes – 0xc0de Nov 01 '16 at 11:22

4 Answers4

22

Turns out it's actually possible. When the function is created, either by using function syntax, or Function constructor, it gets internal [[Call]] property. It isn't a property of function itself, but rather property that any function gets when constructed.

While that only means that anything with [[Call]] could be only Function when it's constructed (well, there is one exception – Function.prototype itself that doesn't inherit from Function), that doesn't mean it cannot become something else later, while preserving [[Call]] property. Well, provided your browser isn't IE < 11.

The thing that allows changing the magic would be __proto__ from ES6, already implemented in many browsers. __proto__ is a magical property that contains current prototype. By changing it, I can make function that inherits from something that isn't Function.

function CallablePoint(x, y) {
    function point() {
        // Complex calculations at this point
        return point
    }
    point.__proto__ = CallablePoint.prototype
    point.x = x
    point.y = y
    return point
}
// CallablePoint should inherit from Function, just so you could use
// various function methods. This is not a requirement, but it's
// useful.
CallablePoint.prototype = Object.create(Function.prototype)

First, the constructor for CallablePoint makes a Function (only Functions are allowed to begin with [[Call]] property. Next, I change its prototype, so it would inherit CallablePoint. At this point I have a function that doesn't inherit from Function (sort of confusing).

After I defined constructor for CallablePoints, I set the prototype of CallablePoint to Function, so I have CallablePoint that inherits from Function.

This way, the CallablePoint instances have prototype chain: CallablePoint -> Function -> Object, while still being callable. Also, because the object is callable, it has according to the specification, typeof equal to 'function'.

Konrad Borowski
  • 11,584
  • 3
  • 57
  • 71
1

I will write my answer assuming that you were after __call__ functionality available in Python and often referred to as "callable object". "Callable object" sounds foreign in JavaScript context.

I've tried several JavaScript engines, but none of those I've tried allows you to call objects, even if you inherit from Function. For instance:

function Callable(x) {
...     "use strict";
...     this.__proto__ = Function.prototype;
...     this.toString = function() { return x; };
... }
undefined
> var c = new Callable(42);
var c = new Callable(42);
undefined
> c;
c;
{ toString: [function] }
> c(42);
c(42);
TypeError: Property 'c' of object #<Object> is not a function
    at repl:1:1
    at REPLServer.eval (repl.js:80:21)
    at repl.js:190:20
    at REPLServer.eval (repl.js:87:5)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream.<anonymous> (readline.js:82:12)
> c instanceof Function;
c instanceof Function;
true
c.apply(null, [43]);
TypeError: Function.prototype.apply was called on 43, which is a object and not a function
    at Function.APPLY_PREPARE (native)
    at repl:1:3
    at REPLServer.eval (repl.js:80:21)
    at repl.js:190:20
    at REPLServer.eval (repl.js:87:5)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
> 

This is V8 (Node.js). I.e. you may have an object, that formally inherits from function, but it is not callable, and I could not find a way to convince the runtime it may be called. I had similar results in Mozilla's implementation of JavaScrip, so I think it must be universal.

However, the role of custom types in JavaScript is vanishingly small, so I don't think you will be missing it that much anyway. But, as you had already discovered, you can create properties on functions, the same way as you can on objects. So, you can do it, just in a less convenient way.

0

I'm not sure if you're aware that your object will only be an instance of CallablePoint if you use the new keyword. By naming it "callable", you make me think you don't want to use new. Anyway, there is a way to force an instance to be returned (thanks for the tip, Resig):

function CallablePoint(x, y) {
    if (this instanceof CallablePoint) {
        // Your "constructor" code goes here.
        // And don't return from here.
    } else {
        return new CallablePoint(x, y);
    }
}

This will return an instance of CallablePoint regardless of how it was called:

var obj1 = CallablePoint(1,2);
console.log(obj1 instanceof CallablePoint); // true

var obj2 = new CallablePoint(1,2);
console.log(obj2 instanceof CallablePoint); // true
bfavaretto
  • 71,580
  • 16
  • 111
  • 150
-1

If you want the CallablePoint() constructor to return an object of type CallablePoint, then you can do something like this where the CallablePoint object contains a point as a property of the object, but remains a CallablePoint object:

function CallablePoint(x, y) {
    this.point = {};
    this.point.x = x
    this.point.y = y
}

or, if what you're really trying to do is to make a function that returns you an object of CallablePoint, then you can create a factor function:

function CallablePoint(x, y) {
    this.point = {};
    this.point.x = x
    this.point.y = y
}

function makeCallablePoint(x, y) {
   return new CallablePoint(x,y);
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979