tc39/proposal-bind-operator

Binding `new`?

Closed this issue ยท 9 comments

The current syntax seems like it would work well with auto-binding constructors as well. Java also has a version for this, with its Class::new shorthand. The same thing could be very useful for JavaScript as well, since classes can't be instantiated without new.

class Foo {
  constructor(value) { this.value = value }
}

const foos = args.map(Foo::new)
const foos = args.map(value => new Foo(value))

Only incidentally, I'm suggesting a very similar syntax. Since new is a keyword in all contexts, it couldn't possibly be an identifier for a bound function, so it would work very well.

As for the suggested semantics, expr::new should function identically to the following when called:

function exprCreate() {
  return Reflect.construct(expr, arguments)
}

More specific semantics:

Syntax:

BindExpression[Yield] :
    LeftHandSideExpression[?Yield] :: `new`

Runtime Semantics: BoundConstructorCreate(target, boundArgs)

  1. Assert: Type(target) is Object.
  2. Assert: IsConstructible(target) is true.
  3. Let proto be target.[GetPrototypeOf].
  4. ReturnIfAbrupt(proto).
  5. Let obj be a newly created object.
  6. Set objโ€™s essential internal methods to the default ordinary object definitions specified in 9.1.
  7. Set the [[Call]] internal method of obj to the [[Construct]] internal method implementation as described in 9.4.1.2.
  8. Set the [[Construct]] internal method of obj as described in 9.4.1.2.
  9. Set the [[Prototype]] internal slot of obj to proto.
  10. Set the [[Extensible]] internal slot of obj to true.
  11. Set the [[BoundTargetFunction]] internal slot of obj to targetFunction.
  12. Set the [[BoundThis]] internal slot of obj to null.
  13. Set the [[BoundArguments]] internal slot of obj to an empty List.
  14. Return obj.

Note: This is equivalent BoundFunctionCreate(target, null, empty List) where target is always a constructor, but with the exception that [[Call]] and [[Construct]] have the same implementation.

Runtime Semantics:

BindExpression :
    LeftHandSideExpression :: `new`
  1. Let targetReference be the result of evaluating LeftHandSideExpression.
  2. Let target be GetValue(targetReference).
  3. ReturnIfAbrupt(target).
  4. If IsConstructor(target) is false, throw a TypeError exception.
  5. Return BoundConstructorCreate(target, boundArgs).

This could be a sweet addition. Being able to new things is useful (and is useful in Java 8 too).

Thanks @IMPinball !

After @dtinth brought up Java method references over in #18, I considered this as well.

I think it would make a lot of sense provided that we split up this proposal into separate "method extraction" and "pipelining" operators. This would fit right in with a method extraction operator.

console::log; // => (...args) => console.log(...args) // or something like that
Array::new; // =>  (...args) => new Array(...args) // or something like that
ssube commented

๐Ÿ‘ Referencing new (and methods) like this is super useful and pairs well with classes, especially when deserializing.

@ssube "deserializing"? Can you explain?

ssube commented

@zenparsing Instantiating classes with data, like xhr.get('example.com/books').then(JSON::parse).then(books => books.map(Book::new)).

@ssube Gotcha.

Rather than request to new syntax, we can just implement it under current frame.

class Foo {}

Foo.new( 'foo' );
array.map( Foo.new );
'foo' |> Foo.new;

Before proposal goes, we can just only do prototype modification to implement it.

Reflect.defineProperty( Function.prototype, 'new', {
	get(){
		const newer= ( ...args )=> new this( ...args, );
		
		Reflect.defineProperty( newer, 'length', { value:this.length, }, );
		Reflect.defineProperty( newer, 'name', { value:`${this.name}.new`, }, );
		
		return newer;
	},
}, );

Could you please not resurrect an issue closed 4 years ago for this? It's not helpful and I'd rather not deal with any more notification spam than I have to.