Skip to content
This repository was archived by the owner on Apr 3, 2025. It is now read-only.
This repository was archived by the owner on Apr 3, 2025. It is now read-only.

Suggesion: Define Operators with functions #56

Open
@Haringat

Description

@Haringat

Analogous to Object.defineProperty/Object.defineProperties we could have Object.defineOperator/Object.defineOperators to define operators for objects/prototypes. This would be in line with current designs for defining things for objects. Like with Object.defineProperty trying to re-define a previously defined operator for an object should throw.

Typescript definition

interface Operators<TFirst> {
    "+"(first: TFirst, second: unknown): TFirst;
    "-"(first: TFirst, second: unknown): TFirst;
    "*"(first: TFirst, second: unknown): TFirst;
    "/"(first: TFirst, second: unknown): TFirst;
    "~"(first: TFirst): TFirst;
    "++"(first: TFirst): TFirst;
    "--"(first: TFirst): TFirst;
    // TODO: add all overloadable operators
}

interface ObjectConstructor {
    defineOperator<TObject extends object, TOperator extends keyof Operators<TObject>>(o: TObject, operator: TOperator, operatorDefinition: Operators<TObject>[TOperator]): TObject;
    defineOperators<TObject extends object>(o: TObject, operatorDefinitions: Partial<Operators<TObject>>): TObject;
}

Examples

const o = {
    counter: 1
};

Object.defineOperator(o, "+", (a, b) => {
    return {
        counter: a.counter + b
    };
});

console.log(o.counter); // 1
o = o + 3;
console.log(o.counter); // 4
Object.defineOperator(o, "+" (a, b) => {
    return {
    };
}); // TypeError: Cannot redefine operator: +
class Foo {
    p = 1;
    constructor(p) {
        this.p = p;
    }
}

Object.defineOperator(Foo.prototype, "++", (a) => {
    a.p++;
    return a;
});

class Bar extends Foo {
    o = 1;
}

Object.defineOperator(Bar.prototype, "++", (a) => {
    a.p++;
    a.o++;
    return a;
});

Design goals

  • Expressivity:
    • Support operator overloading on both mutable and immutable objects, and in the future, typed objects and value types. (would work)
    • Support operands of different types and the same type, as in the above examples. (would work, in TypeScript the second operator would have to be assumed to be either any or unknown)
    • Explain all of JS's behavior on existing types in terms of operator overloading. (I am unsure what the author meant with that)
    • Available in both strict and sloppy mode, with and without class syntax. (would work, perhaps some syntax sugar for classes could be invented)
  • Predictability
    • The meaning of operators on existing objects shouldn't be overridable or monkey-patchable, both for built-in types and for objects defined in other libraries. (They would be overridable in class hierarchy, but not monkey-patchable on the same object and not definable for primitive values)
    • It should not be possible to change the behavior of existing code using operators by unexpectedly passing it an object which overloads operators. (If this is feasible.) (This goal would be violated, however very unlikely since code written without operator overloading in mind would not use operators on objects and thus would not change behavior. Changing the behavior of overloaded operators of 3rd party code is impossible, as the different definitions would create an error.)
    • Don't encourage a crazy coding style in the ecosystem. (I would never do that :) )
  • Efficiently implementable
    • In native implementations, don't slow down code which doesn't take advantage of operator overloading (including within a module that uses operator overloading in some other paths). (Would be violated because the implementation would need to check for the presence of the internal slot on the object. In practice that would not make much difference as operators would probably only be used on objects on which they are overloaded since the current language-definition for the result of using operators on objects does not yield useful results for most cases)
    • When operator overloading is used, it should lend itself to relatively efficient native implementations, including - In the startup path, when code is run just a few times - Lends itself well to inline caching (for both monomorphic and polymorphic cases) to reduce any overhead of the dispatch - Feasible to optimize in a JIT (for both monomorphic and polymorphic cases), with a minimal number of cheap hidden class checks, and without extremely complicated cases for when things become invalid - Don't create too much complexity in the implementation to support such performance
    • When enough type declarations are present, it should be feasible to implement efficiently in TypeScript, similarly to BigInt's implementation. (see definitions above)
  • Operator overloading should be a way of 'explaining the language' and providing hooks into something that's already there, rather than adding something which is a very different pattern from built-in operator definitions. (would be followed)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions