Open sirisian opened 4 years ago
I like the idea of making more ergonomic syntax for classes with operator overloading. Do you have an idea of the algorithm you'd use for determining which overload to select? Do you like the one described in this proposal, or do you prefer another one?
What about decorators? Could a built-in decorator be used, similar to what AssemblyScript does?
class Vector {
@operator("*")
static mul(left: Vector, right: Vector): Vector { ... }
@operator("*")
mul(right: Vector): Vector { ... }
}
As a side-effect, you can also use the method directly:
vec.mul(otherVec)
I too dislike that we need to make two parallel class
hierarchies in order to achieve operator overloading with the current proposal. It would be much more ergonomic to define a single Vector3
class (for example) with or without it having operator overloads (instead of one class boxing another class).
I proposed decorators in a previous draft that you can see in the git log, but removed them due to both negative feedback about the syntax and uncertainty about how decorators will go exactly.
The second new feature would be the ability to define extension classes. Currently declaring a class with the same name causes a redeclaration SyntaxError. I'd propose that if the class only has, for now, operators that it's treated as an extension and would simply act like a partial class merging into any previously declared class as long as there are no signature conflicts in the operator overloads.
I would propose to extend mixins with operators for that usecase.
https://github.com/justinfagnani/proposal-mixins
Example using parts of your code:
mixin Matrix4x4Mixin {
constructor(...m) {
super(16);
this.set(m);
}
operator+(m) {
return new this.constructor(...this.map((value, index) => value + m[index]));
}
clone() {
return new this.constructor(...this);
}
static Identity() {
return new this(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
}
class Matrix4x4 extends Array with Matrix4x4Mixin {
}
class Matrix4x4Int8 extends Int8Array with Matrix4x4Mixin {
}
Instead of using operator
methods, isn't it more ergonomic to use Symbol?
Instead of
class Point {
operator+(rhs) {}
operator==(rhs) {}
operator!=(rhs) {}
}
We could use
class Point {
[Symbol.operator["+"]](rhs) {}
[Symbol.operator["=="]](rhs) {}
[Symbol.operator["!="]](rhs) {}
}
We could use
class Point { [Symbol.operator["+"]](rhs) {} [Symbol.operator["=="]](rhs) {} [Symbol.operator["!="]](rhs) {} }
I like this one the most out of all suggestions I have read. The only problem with this approach is that unary +
(as in +foo
) and binary +
(as in foo + bar
) need to be differentiated somehow.
Perhaps we can over-specify things a bit, like this?
class Point {
[Symbol.operator.unary['+']]() {}
[Symbol.operator.unary['++']]() {}
[Symbol.operator.binary['+']](rightOperand) {}
[Symbol.operator.binary['??']](rightOperand) {}
[Symbol.operator.assignment['+=']](value) {}
// or Symbol.operator.assignment['+'](value) {}
[Symbol.operator.comparison['==']](other) {}
/*** And probably (or probably not)... ***/
// Interchangeable with handler.deleteProperty()
[Symbol.operator.keywords['delete']](property) {}
// Interchangeable with handler.has()
[Symbol.operator.keywords['in']](property) {}
// Interchangeable with handler.construct()
[Symbol.operator.keywords['new']](property) {}
// await seeking for equity.
[Symbol.operator.keywords['await']](property) {}
}
Some already has their own well-known Symbol
s: instanceof
(Symbol.hasInstance
), for (... of ...)
(Symbol.iterator
), for await (... of ...)
(Symbol.asyncIterator
), etc. Some others with (in my opinion) concrete meanings might not even be overloadable: ?.
, await
, yield
, typeof
, void
, etc.
Not to mention, Symbol.toPrimitive
has already provided a way to overload coercion:
class C {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 42;
case 'string':
return 'Hello';
default:
// or case 'default':
return null;
}
}
};
console.log(+new C()); // 42
console.log(`${new C()} world!`); // 'Hello world!'
console.log(new C() + 'ish?'); // 'nullish?'
This would keep all the current static behavior with the goal of using a more future-proof (for types) syntax choice.
Syntax for each operator:
This would use a fake method overloading syntax to specialize for each right hand side type.
Ad-hoc examples:
The second new feature would be the ability to define extension classes. Currently declaring a class with the same name causes a redeclaration SyntaxError. I'd propose that if the class only has, for now, operators that it's treated as an extension and would simply act like a partial class merging into any previously declared class as long as there are no signature conflicts in the operator overloads.
There is a caveat here with the extension syntax. It's elegant for user made classes and extending them in compartmentalized ways, but this syntax doesn't work for intrinsic objects. Things like Boolean, Number, BigInt, String, etc. You can't just write:
You'd need to use the function syntax, like the spec proposal has, to declare them. Maybe I'm thinking about this part wrong, but it makes my suggestion awkward as ideally you'd want a single elegant syntax throughout a codebase without having to use two separate systems. If there was a way to refer to the intrinsic objects and use an extension syntax that would be awesome, but it's not clear to me if that's possible. (In an elegant and consistent way within JS).