Home

Awesome

Symbol.operator

This proposal introduces the Symbol.operator and Symbol.unaryOperator functions to the ECMAScript Standard Library. The Symbol.operator and Symbol.unaryOperator functions can be used to extend prototypes to overload operator behaviours, with a future scope of being able to easily add new operators.

Symbol.operator and Symbol.unaryOperator behave a lot like Symbol.for in that they search for existing symbols in a runtime-wide symbol registry with the given key and returns it if found. However, these registries exist separately from Symbol.for - so calling Symbol.for('+') will return a different symbol to Symbol.operator('+') which will return a different symbol to Symbol.unaryOperator('+').

Where an operator is used with two operands of the same type, a lookup begins for <rho>.__proto__.constructor[Symbol.operator(<operator>)] (for example, the + operator will look up <rho>.__proto__.constructor[Symbol.operator('+')]). If the property exists and is a function, then the function is called, with the left hand operand as the first argument and the right hand operand as its second argument. The function's return value is what will be used as the result for the operarion.

Where an operator is used with one operand, for example the ! operator, the single operand will be evaluated to check the existance of the <operand>.__proto__.constructor[Symbol.unaryOperator(<operator>)] property (for example the ! operator will look up <operand>.__proto__.constructor[Symbol.unaryOperator(!)]). If the property exists and is a function, then the function is called with the operand as the first and only argument. The function's return value will be used as the result of the operation.

Operators may additionally coerce return values for comformity across the platform. For example the ! operator will coerce any return value ToBoolean. As another exampe, the unary + operator will always coerce ToNumber to maintain compatibility with existing expecation in areas like asm.js.

Supported Operators

The proposed supported operators are:

Exceptions

The following operators are not supported:

Examples

Example 1

class Thinger() {
  constructor(...things) {
    this.things = things
  }
  
  static [Symbol.operator('+')](leftHandOperand, rightHandOperand) {
    return new Thinger(...[...leftHandOperand.things, ...rightHandOperand.things])
  }

}

const thingerA = Thinger('a')
const thingerB = Thinger('b')
const thingerAB = thingerA + thingerB
console.assert(thingerAB instanceof Thinger, 'the returned value from the `+` operator is a new Thinger instance')
console.assert(thingerAB.things[0] === 'a' && thingerAB.things[1] === 'b', 'the returned instance is created from the logic of the static method against both operands')

console.assert((thingerA + 1) === 1)

Future: Extending of operators

Should one want to add an operator to the language, it could become as simple as implementing a new symbol a prototype; for example the bind operator :: could simply be polyfilled with the following:

Function[Symbol.operator('::')] = function (lho, rho) {
  return rho.bind(lho)
}

How could this even work?

When parsing a piece of javascript, when the tokenizer reached an unrecognised token between one or two operands (with, perhaps a whitelist of certain allowed operator characters, for example any Unicode character betwixt U+2000–U+218F), it could search in the operator registry (perhaps at runtime? during evaluation?) for the operator, and if not found could raise a SyntaxError.