Function object initializer

Functions are objects, callable objects. Regular object initializer benefits don't apply to callable objects like spread operator and __proto__ initializer
To be more inclusive shouldn't we make a way to define a callable object as a regular one?
I don't know what property name to use to be backward compatibile, but I start proposing the keyword function:

f = {
  function(parameters) {
    // …
  },
  // or (exclusive)
  function: (parameters)=>{
    // …
  },
  // or (exclusive)
  function: {
    function(parameters) {
      // …
    },
    // other properties
  },


  // and the rest of goodies:
  a: "foo",
  b: 42,
  c: {},
  1: "number literal property",
  "foo:bar": "string literal property",

  shorthandProperty,

  method(parameters) {
    // …
  },

  get property() {},
  set property(value) {},

  [expression]: "computed property",

  __proto__: prototype,

  ...spreadProperty,
};

Like the use of __proto__, any second function property to be an error or any non callable object as value of function to be an error as well.

In this example f will be f.function with all the properties assigned to f as spread operator, not as Object.assign(). Today we can only use Object.assign() to load a function object with properties. The __proto__ value to be applied to f, replacing the prototype chain of f.function. After initialization, the function accessor is not needed anymore, it shall disapear.
Edit 1:
If function accessor remains we can get another benefit which current stage of ES is missing: callability inheritance: If another object o takes f in its prototype chain, o becomes callable by the presence of function property.

Reminds me of TypeScript's syntax for defining a call signature as part of an object type, that doesn't use a keyword and instead is like an anonymous method

{
  prop: string;
  (arg: number): number; // type is callable
}

I don't know where to put the column sign :, after ), before { ?

(parameters) {// function body}
(parameters)=>{// arrow function body}
*(parameters) {// generator body}
async (parameters) {// async function body}
async *(parameters) {// async generator body}

Another idea is to use a symbol defined as Symbol.callable

Before worrying about the syntax, what’s the motivation and use case? Why is this needed?

2 benefits I see:

  1. Setting the [[Prototype]] with __proto__ on initialization is faster than Object.setPrototype() afterward.
  2. Spreading an object’s properties into a function object is not possible. Only Object.assign() works.

How often do you need a function with a different prototype, and with extra properties? Either seems rare to me, let alone both.

1 Like

I do worry about the effect of having callable objects appear in JavaScript.

Today, if you want to check if something is callable, you can just do typeof value === 'function'. With something like this, that wouldn't be the case anymore. (In fact, we would probably need to create a new function for end-users to use to decide if something is callable - e.g. globalThis.isCallable()).

This means these new callable objects may not work well in a number of existing libraries - e.g. I took a quick look at Lodash's _.find() as just an example, and it looks like it'll use a typeof value === 'function' style of check to decide how your callable would behave when passed in. From the end-user's perspective, this isn't the worst thing, you just wrap it in an arrow function and it works (after you've debugged for a long time to figure out why it's not working). From a library maintainer's perspective, now you need to update all of your libraries to use isCallable() instead of typeof value === 'function' if you want your library to provide good support for the latest JavaScript.

I think it would be best if we stuck with only functions being callable. Maybe a shorthand syntax could be provided so we can make functions with additional properties, on the fly, but then again we already have the following:

Object.assign(
  function() {
    ...
  },
  {
    x: 1,
    y: 2,
  }
);

typeof x === "function" and "x is a "callable object"" are exactly equivalent statements.

One exception if x is a "class" or generator. e.g.

class A{};
typeof A; // function
A(); // Uncaught TypeError:...

"x is a function" doesn't mean it can't throw an error unconditionally. Any function can be implemented that way. However, you can proxy its apply method and make it not throw.

1 Like

Classes are callable (they still have the [[Call]] internal method), they just throw as soon as you call them. But as a callable you are able to trap apply in a Proxy and prevent calls from throwing. You can't do this with non-callables.

let MyClass = class MyClass {}

MyClass = new Proxy(MyClass, {
  apply(target, thisArg, argumentsList){
    return new target(...argumentsList)
  } 
})

console.log(new MyClass()) // MyClass {}
console.log(MyClass()) // MyClass {}

Generator functions are also callable, though the generator (non-function) objects they return when called are not.

Edit: Josh-Cena beat me to it ;)

2 Likes