-
Notifications
You must be signed in to change notification settings - Fork 33
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Operator overloading and custom operators #10
Comments
I originally wanted to request macros and the issue was titled Of Macros and Men 😄 |
@zolomatok to replace I think some kind of operator customizing could be useful. It's a big feature and will take a bit of thought. One big difference is that Coffee/ES/JS is duck typed so it's not as easy to attach an operator to a type or a class. It might be more possible with TypeScript integration. There's already a little bit of similar features with Thanks for the feedback! |
ah, you're completely right, of course, thanks, awesome!
It was an absolute joy to learning about op overloading when I was learning Swift. Operators lend so much legibility to the code compared to whatever handwaving one needs to do in leu of them. Not only are they infinitely less verbose than calling functions on objects, they provide much better semantics. I remember try a reactive library in some language where combining observables went something like (pseudocode):
and this kind of method calling suggests a kind of hierarchy between
This kind of friendly, concise syntax is right in line with Civet and Coffee I think.
Could you please elaborate on why that makes it harder? I don't know that much about the intricacies of compilation. |
At compilation time, we generally have no idea whether (first + second).listen -> ... might become (first.__plus__ ? first.__plus__(second) : first + second).listen(function () { ... }) So there would be runtime overhead for this, but I think it would be great to add as a togglable option via (By contrast, in statically typed languages, the exact type of |
Ah, makes sense, thanks @edemaine ! |
This is definitely in scope for Civet and I think I have a decent idea of the specification of how things could work. This will reconcile and improve the CoffeeScript/JS Here's some examples that I thought about for discussion.
Overloads will currently only be defined per file. They will be checked bottom to top, the first matching one will apply so generic overloads can be declared above more specific overloads. Overloads cannot change precedence, associativity, or add new operators that don't exist in JS. Notes |
luv it!! 🌈
Does |
The obvious sensitivity here is, if you're relying on the type system, you might not call the correct operator if you don't have types set correctly. But I guess you'd likely get the wrong type output as a result (e.g. vanilla The other obvious big thing is that Civet would need to actually talk to TypeScript during compilation phase in order to know types while transpiling. Currently, only the language server does this, so it's a big structural change to Civet. But it does seem exciting what it could enable... On the other hand, it makes me wonder: should these features be added to TS before Civet? One important detail is how to import {operator+, operator<<} from './foo.civet' (or the same without OverloadingOne unusual thing here is that we're overloading functions. In TS, function prototype overloading already exists. But it's unusual (even in TS) to have multiple function bodies for different prototypes. I agree that it makes special sense for operators, because there's only one function combine(a: number, b: number): number {
return a * b;
}
function combine(a: string, b: string): string{
return a + ' ' + b;
}
combine(3, 3) // 9
combine('hello', 'world') // 'hello world' Compilation would turn this into two functions, Identifier OperatorsI really like the idea of overloading operator union<T>(a: Set<T>, b: Set<T>): Set<T> {
return new Set<T>(...a, ...b);
}
declare const a: Set, b: Set
const c = a union b
// now invalid: union a, b I assume
Yes, it's shift left from C. |
Combining my earlier ideas of runtime type checking with Daniel's latest, here's a possibility for operator redefining (not overloading) that might be an interesting first step toward operator overloading (which IMO is as hard as function overloading, a significant task): // Redefines the meaning of + in this file. Untyped version.
operator+(a, b) {
if Array.isArray(a) and Array.isArray(b)
return [...a, ...b]
else
return a + b
}
// TS types for above
operator+(a: Array, b: Array): Array
operator+(a: number, b: number): number
operator+(a: string, b: number): number
operator+(a: number, b: string): number
operator+(a: string, b: string): string The idea here is that there can be only one If there are multiple libraries that offer their own import {Vector, operator+ as vectorAdd} from './vector.civet'
import {Magic, operator+ as magicAdd} from './magic.civet'
export operator+(a, b) {
if a instanceof Vector or b instanceof Vector
return vectorAdd(a, b)
else if a instanceof Magic or b instanceof Magic
return magicAdd(a, b)
} I'm not claiming this is convenient, but it's a much easier first target on the way to full operator overloading, and could already be quite useful, especially for projects that only want to redefine each operator once (e.g. they just want |
I think untyped, per file redefinitions would still be most of the work to implement but lack a lot of the benefits of mixing types and operators (multiply a point through a matrix, add a scalar to a point). We wouldn't necessarily need to hook into the entire TS system for the initial version. A very basic implementation would match literal string to string for types and not know anything about TS at all (just might need to cast explicitly if your type is more complex) I also think the implementation of operators would be closer to macros than to functions so overloading the function type wouldn't quite make sense. My hunch is that it is a direct transformation on the array of binary op and expression tokens. Basically starting at the highest precedence operators, checking for operators defined on the explicit exact string match type, rewriting the tokens substituting in the "return type" string and then repeating at the next layer up. For Identifier Operators I'd rather add additional symbol operators first. I'm still on the fence but adding custom operators by string names could make sense then Additional note: comparison operators should still chain: operator <=(a: Set<any>, b:Set<any>) {
Array.from(a).every(x => b.has(x))
}
declare const s1: Set<any>, s2: Set<any>, s3: Set<any>
s1 <= s2 <= s3 // Array.from(s1).every(x => s2.has(x)) && Array.from(s2).every(x => s3.has(x)) Another fun one to consider: operator +(...objs: Object[]) {
Object.assign(...objs)
}
declare const a: Object, b: Object, c: Object
x := a + b + c // const x = Object.assign(a, b, c) |
Just to update this issue, Civet now has a simple form of custom infix operators (no overloading). Check out the docs. It's designed to be compatible with the future according to the above proposals for e.g. |
Thanks for the heads up @edemaine, this is pretty freaking awesome! 🚀 |
TypeScript integration is an ideal solution, but hard to implement. There's also hacky, but much simpler solution - alter JS runtime. a) Convert every operator to function call, and b) add those functions to built-in JS objects. Like Actually, to do that there's no need to even modify Civent, it could be implemented as post-Civet JS transformation, with babel transformer |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
This is a feature request rather than an issue. Originally I wanted to inquire about macro support, but to be honest what I'm mostly looking for is operator overloading and custom operators.
Swift has a wonderful feature where you can overload prefix, infix and postfix operators or create new ones to your heart's content and it's the best. It has the potential to make the code very succinct so such feature I believe is such an obvious fit in Civet and CoffeeScript.
For example, for a long time, Swift had an awfully verbose regex matching API, but I could just define the
~=
operator onString
and I could express with two characters that otherwise would have taken 4 lines to do:url ~= ".*/account/.*/login"
Or how about using operators for matrices? Makes so much sense.
Or how about some reactive code where you could just add together multiple observables to get a combined signal?
Or how about a language that uses
:=
as a readonly shorthand? 😉Mathematical operators is one of the best known and most succinct language we have as humans and I feel like it's such a waste to use them only for numbers.
I would really really love a Swift-like solution. 🚀
The text was updated successfully, but these errors were encountered: