Callable objects protocol

# Sultan (6 years ago)

Something along the lines of Symbol.iterator protocol for defining callback objects i.e: Symbol.callable:

const obj = { [Symbol.callable]: function (...args) { return this[Symbol.for('value')] }, [Symbol.for(''value')]: 'value', }

assert(obj() === 'value')

# T.J. Crowder (6 years ago)

On Tue, Dec 4, 2018 at 5:02 PM Sultan <thysultan at gmail.com> wrote:

Something along the lines of Symbol.iterator protocol for defining callback objects i.e: Symbol.callable:

That's interesting. What's the primary motivation? Binding the data? That's easily done with a closure ([fiddle1):

const obj = (() => {
    let value = 'value';
    return () => value;
})();

console.log(obj() === 'value'); // true

It's a bit more verbose than one would like, but usually that's washed out by the implementation (which is really trivial above).

You can also do it with a Proxy on a function, but it doesn't really buy you anything.

Be interested to know more about why you want non-function callable objects.

-- T.J. Crowder

# Jordan Harband (6 years ago)

What would typeof return on a callable object? There's a lot of code on the web that uses a typeof of "function" to determine if something is safe to call. There's also a lot of code on the web that uses a typeof of "function" to do type detection. How would you suggest a callable object navigate these potentially conflicting constraints?

# Isiah Meadows (6 years ago)

BTW, typeof new Proxy({}, {apply() { return 1 }}) returns "object", not "function". Not sure this is any different.


Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# Logan Smyth (6 years ago)

The type of the target is what affects typeof, indirectly by setting the [[Call]] value. If an apply or construct trap handler is given and the target isn't a function, they are ignored. See step 7 in www.ecma-international.org/ecma-262/9.0/index.html#sec

# Isiah Meadows (6 years ago)

BTW, there are proxies 1, and one of the proxy hooks is to intercept calls 2.

Your "callable object" proposal would be literally as simple as this to implement:

const callable = Symbol.for("callable")
const handler = {
    apply(target, thisArg, argsList) {
        return Reflect.apply(target[callable], thisArg, argsList)
    },
}
function makeCallable(obj) { return new Proxy(obj, handler) }

// Your example, ported
const obj = makeCallable({
    [callable]: function (...args) { return this[Symbol.for('value')] },
    [Symbol.for(''value')]: 'value',
})

assert(obj() === 'value')
obj[callable] = () => 1

assert(obj() === 1)

Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# Isiah Meadows (6 years ago)

Edit: the wrapper needs to be a function, so ignore that last email. It's wrong.


Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# Ranando King (6 years ago)

Ok.... maybe I'm thinking a little to literally, but isn't a function already a callable object?

function obj() {
  return obj.value;
}
obj.value = "value";

assert(obj() === "value");
# Ranando King (6 years ago)

Thinking again, this might be a request for static lexical scope variables such that:

function obj() {
  static value = { test: 42 };
  return obj.value;
}

var a = obj();
assert(obj() === a);
# Sultan (6 years ago)

Yes function objects are already callable objects. This is meant to allow authors the ability to make callable non-function objects with this new protocol.

typeof nonFunctionCallableObject === 'object'. As much as Symbol.iterator is used to determine if a non-native object is an iterator so too would Symbol.callable with to non-function callables.

One of the utilities of this can be visualized in the getter/setter type callables: fn() gets the value, fn(a) sets the value, this is normally supplied with methods to allow an outsider the ability to be reactive to changes of the underlining value something akin to observables.

One way to implement this is as T.J mentioned – using a closure:

function closure () { var value = 'value' return function (a) { return arguments.length ? value = a : value } }

Another would be to treat functions as the objects they truly are:

function object () { function value (a) { return arguments.length ? this.value = a : this.value } value.value = null }

Or as this proposal would allow;

An idiomatic class-based implementation with a shared callable protocol that is extendable by other classes:

class prototype { Symbol.callable { return args.length ? this.value = args[0] : args[0] } }

const a = new prototype()

assert(a(1) === 1, a() === 1)

# Ranando King (6 years ago)

Ok. I get what you're after. So what's to be gained by being able to make an object directly callable over simply using functions? I remember that in C++ there was the concept of a "functor" where overloading operator() would allow you to treat instances as functions, and that had some limited utility to it (like creating delegate functions). But those use cases are already easily possible in ES by other means.

# Andrea Giammarchi (6 years ago)

How about this:


// the poly
if (!Symbol.callable)
  Symbol.callable = Symbol('callable');

// the setup
class Callable extends Function {
  constructor(object) {
    super('return arguments.callee[Symbol.callable](...arguments)');
    //            sloppy mode FTW!
    Object.setPrototypeOf(this, object);
  }
}


// the example
const obj = new Callable({
  [Symbol.callable](value) {
    return value + this.value;
  },
  value: 123
});

obj(7); // 130


# Ranando King (6 years ago)

Maybe I asked it wrong.

How is making an ordinary object callable at all useful for anything that can't already be easily handled via objects and functions? (looking for use cases here) How does this make coding easier to do and understand? (for the AST parser and for the human)

# Andrea Giammarchi (6 years ago)

I've actually replied to the op, I didn't mean to answer you directly, but the only reason I wrote that is because I could, no other reasons.

However, people unaware of the handleEvent pattern for event listeners often hope to be able to pass objects as listeners, ignoring the fact they can do that already (but they need a handleEvent method, own or inherited, in that object).

There is at least another use case I can't remember now, but I do remember doing the Proxy dance before ending up realizing that the apply hook needs objects anyway.

But yeah, I don't think it's a must have, specially because we can have something similar already, as shown in my example.

# Andrea Giammarchi (6 years ago)

the apply hook needs objects anyway.

I meant functions

# Isiah Meadows (6 years ago)

Personally, I'd prefer something else: a means of a function object literal that's still callable, but I can tack other properties to it easily. Something like this, maybe:

{
(...args) { ... },
}

In this, the this value is set to the callee itself, not the given this value.

Not married to the syntax, but I want the functionality.

# Ranando King (6 years ago)

That's the kind of thing I was shooting for with static lexical scope variables. There's 2 problems with it given the way things are going though. Take a look.

function foo() {
  static a=1,
         b=2,
         c=3;
}

By the way I'm thinking, this would create 3 static variables within foo that are only initialized once and retain whatever value is set on them across invocations. Basically, the object foo carries around a closure containing those values. Problem is, this is private to foo. That conflicts with class-fields and it's sigil-means-private model.

Ignoring that, public static variables can also be done (but it'd be the first ever introduction of public in ES.

function foo() {
  static public a=1,
                b=2,
                c=3;
}

This would make foo.a, foo.b, & foo.c accessible as public properties of foo.

Think this needs to be a proposal?

# Andrea Giammarchi (6 years ago)

I don't think introducing public here has any value. We have static #a = 1 eventually for private already, the default should follow the classes behavior, IMO

# Ranando King (6 years ago)

Andrea, if class-fields becomes the accepted standard for private then that would work, but it's awfully redundant. A closure is the very definition of "private" in ES. So what does it mean to have a static #foo lexically declared? From inside the function, it would be no more private than var bar. That would lead to confusion. See the problem?

# Isiah Meadows (6 years ago)

BTW, another reason I want that kind of "function literal" is because I deliberately want to not just expose a function, but expose one with properties, as part of a public API.


Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com