Open adrusi opened 13 years ago
I'm all for this if we can find a simple, clever implementation. Some consideration off the top of my head:
a <+> b
=> a['+'](b)
a <+> b
=> __['+'](a, b)
Math
?)a <add> b
=> add(1, 2)
Matrix::\+
could read well if we went with the method pattern, but they'd be horrible if used in JS code.Ok, how about replacing all uses of an overloaded operator within the scope that the operator was overloaded. I don't know if this is possible without executing the code during compilation, but how about only changing it on values that have the overloading method.
Oh, and I don't think a binary function would do it, it would prevent specifying only certain objects to have redefined operators.
only changing it on values that have the overloading method
How? Sample code and compilation would be great.
Array.prototype["@<<_"] = (val) ->
@push val
arr = [1, 2, 3]
arr << 4
1 << 3
compiles to:
Array.prototype["@<<_"] = function(val) {
this.push(val);
};
var arr = [1, 2, 3];
arr["@<<_"](4);
1 << 3;
Again, I have no idea if this is possible, but the function I linked to in the beginning would let you do:
Array.prototype["@<<_"] = function(val) {
this.push(val);
};
var arr = [1, 2, 3];
operate("infix")(arr, "<<", 4);
operate("infix")(1, "<<", 3);
and the operate function would decide whether to use the real operator or the method.
Precedence could be resolved by parsing operators with the highest precedence first, and those with lowest last.
Array.prototype["@<<_"] = (val) -> @push val arr = [1, 2, 3] arr << 4 1 << 3
Not easy. We don't have a way to infer arr
as an Array::
descendant.
operate("infix")(arr, "<<", 4);
Why not simply arr['@<<_'](4)
?
operate("infix")(arr, "<<", 4);
figures out if arr
is an array at runtime, so the compiler doesn't have to do partial execution. Here's the source of operate.
figures out if
arr
is an array at runtime
For what? Your compilation figures out 1 << 3
isn't overloaded on compile time.
edit: Uh I see now. Like I said in the first response, It shouldn't interfere with normal code. It can't slow down all other operations.
Precedence could be resolved by parsing operators with the highest precedence first ...
You mean, using JS operators'? What about custom operators?
I wasn't thinking about custom operators, I don't see what advantage they have over functions, especially as function sin coco don't require parens. You could look into languages like haskell to see how they handle infix function precedence, but I believe that they are just evaluated left to right:
[] <append> 1 <append> 2 <append> 3 <append> 4
is interpreted as
((([] <append> 1) <append> 2) <append> 3) <append> 4
how about overloading of native operators as an option that's off by default. "use overloading"
? And maybe having the option be enabled only for the scope in which it was declared.
and you are very right to worry about performance of this, I tested
# coffeescript because I already have custom textmate commands for it -_-
for i in [0...10000000]
i * i
versus
for i in [0...10000000]
operate("infix") i, "*", i
the second took 11 seconds according to the unix time
command, and the first took only 4.5.
I think that having options to enable and disable it for the current scope (with disabled by default) would be useful. I know it might not sound great to have compiler options, but ES5 has them, so there's a precedent.
You could look into languages like haskell to see how they handle infix function precedence
Haskell allows you to pick a precedence. http://www.haskell.org/onlinereport/decls.html (4.4.2 Fixity Declarations)
"use overloading"
?
Though inelegant, simply replacing every occurrence of operators under that declaration would be easy.
I don't know haskell's syntax, so I can't really understand that document. An elegant way to set precedence would be:
expand <append> after <+> (array, value) -> ...
That might be somewhat hard to parse because you would have to modify operator precedence on the fly.
simply replacing every occurrence of operators under that declaration would be easy
Here's a proof of concept: https://gist.github.com/982183
$ cat 60.co
$op = (op, left, rite) ->
switch op
case \<< then left.push rite; left
default ...
let
"use overload"
console.log [1 2 3] << 4
console.log 1 << 3
$ coco -r ./use_overload.co 60.co
[ 1, 2, 3, 4 ]
8
That looks great, though I assume that there would be some syntactic sugar for $op
, and it wouldn't be a bad idea to use if ... else if ... else
instead of switch
because it's important to be able to test for the type of a value in addition to the operator being used.
As you said, "use overload"
is inelegant, but it's not ugly. I would also suggest something along the lines of "don't use overload"
hopefully with nicer wording than that.
It's not quite as elegant of an operator overloading implementation as ruby's, but it's as good a Haskell's, and better than perl 6's infix functions.
Thanks for considering my suggestion :)
though I assume that ...
Yup, it's just a quick example. You can tweak away the gist for your need (and reach to a more elegant solution along the way ;).
Ok, I'll see if I can understand the parsing going on.
Funny, I thought of a similar idea a few days ago. I was thinking of it in terms of Scala's similar feature, where a b c
is shorthand for a.b(c)
, and operators are valid identifiers (I think that's how it handles it, at least). My conclusion was that the best JS equivalent would be a["b"](c)
for when b is an "operator".
I think the best approach to this problem would be to disallow overloading of built-in operators altogether, and simply have a blacklist of pre-, post- and infix operators (i.e. the built-in ones). I think it also clarifies that "these operators are built-ins" when skimming through code.
Regarding operator precedence, I wouldn't bother at all. I'd simply see it as syntactic sugar for regular function invocation. IMHO I'd like the following compilation:
a + b ++ c <+> d
to
a + b["++"](c["<+>"](d))
a + b ++ c <+> d
toa + b["++"](c["<+>"](d))
That's actually pretty close to what we already have:
$ coco -bpe 'a + b\++ c\<+> d'
a + b['++'](c['<+>'](d));
Oh, that's quite nice actually. I guess I'm fine with that syntax. :)
The problem with the scala syntax is that a b c
already compiles to a(b(c))
. Also infix functions would need a different syntax than <fn>
because 1 <fn> 2
is ambiguous with fn = 5; 1 < fn > 2
+1
Operator overloading is a great feature of languages like ruby and c++, especially for things like matrix arithmetic. I wrote a function for operator overloading (ironically in coffeescript) that works a lot like ruby's operator overloading where operators are methods.
The code is here.
I don't know much about parsing, and as coffeescript is strongly against special functions, I figured I'd ask here. It shouldn't be too hard to implement if you know what you're doing.