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

I'm warming up to the idea of a shorthand object-function declaration. Besides allowing to declaratively create functions that have own properties, it would make it simpler to create unbound functions that have no construction behavior.

First some recap on the 3 kinds of function declarations that exist in the language today:

  • function() {} creates a function sensitive to a this arg that can also be called as a constructor
  • () => {} aka arrow functions create a function with a bound this arg that cannot be called as a constructor
  • foo() {} short hand methods (in object literals or class definitions) create a function that is this arg sensitive but cannot be constructed.

The only way to create the latter kind of function today is to use a temporary object literal and extract its short hand method.

Short hand object-functions, { () {} }, would simplify this, while enabling custom prototypes.

const foo = {
  (bar, baz = null) { ... },
  __proto__: CustomFunction.prototype,
  expando: 42,
};

Would be equivalent to:

const foo = Object.setPrototypeOf(
  Object.defineProperties(
    { foo(bar, baz = null) { ... } }.foo,
    Object.getOwnPropertyDescriptors({ expando: 42 }),
  ),
  CustomFunction.prototype,
);
1 Like

I get that it comes from the point of "define a callable object", but the syntax feels wrong to me. I think I also found one better objection than "feels wrong" ;) How would you define an async callable? Putting async before parameters (like you do with arrow functions) would create an ordinary object with a method named "async". Putting it after the parameters or before the object also feels weird.

I prefer "define a function with extra properties":

function foo {
  __proto__: CustomFunction.prototype,
  expando: 42,
} (bar, baz = null) { ... }
1 Like

If we provide a way to set the prototype, would there be a restriction to require Function.prototype to be in that prototype chain? (So functions like .call() are automatically available). It's also not very common to be manually setting the prototype of objects in JavaScript anymore, the other option would be to (instead? In addition to?) provide syntax for classes to make their instances callable - if we're wanting to go strong after this custom prototype thing.

Such a requirement wouldn’t work well because of Function.prototype from other realms. In general, you can’t actually rely on methods being available on arbitrary objects, so if this is a concern, you’d Function.prototype.call.call()

Arf right, async, function and even this are valid names for properties in object literals.

I suppose your syntax would work, but it loses the non-constructible aspect I was hoping to keep (I can't imagine a use case where a constructible function couldn't be a class constructor in the first place)