Skip to content
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

Feature: operator overloading #60

Open
adrusi opened this issue May 19, 2011 · 22 comments
Open

Feature: operator overloading #60

adrusi opened this issue May 19, 2011 · 22 comments

Comments

@adrusi
Copy link

adrusi commented May 19, 2011

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.

@satyr
Copy link
Owner

satyr commented May 19, 2011

I'm all for this if we can find a simple, clever implementation. Some consideration off the top of my head:

  • There is no one clear way to implement it:
    • As method: a <+> b => a['+'](b)
    • As binary function:
      • a <+> b => __['+'](a, b)
        (Would require a method holder for symbols that can't be identifier. Maybe Math?)
      • a <add> b => add(1, 2)
        (Haskell-like. Directly calls whatever lexically available.)
    • Precedence, how do we resolve?
  • It's an esoteric pattern, so:
    • It shouldn't interfere with normal code. This makes replacing every operations a non-option.
    • The outcome should play well with JS world. APIs like Matrix::\+ could read well if we went with the method pattern, but they'd be horrible if used in JS code.

@adrusi
Copy link
Author

adrusi commented May 19, 2011

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.

@adrusi
Copy link
Author

adrusi commented May 19, 2011

Oh, and I don't think a binary function would do it, it would prevent specifying only certain objects to have redefined operators.

@satyr
Copy link
Owner

satyr commented May 19, 2011

only changing it on values that have the overloading method

How? Sample code and compilation would be great.

@adrusi
Copy link
Author

adrusi commented May 19, 2011

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.

@satyr
Copy link
Owner

satyr commented May 20, 2011

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)?

@adrusi
Copy link
Author

adrusi commented May 20, 2011

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.

@satyr
Copy link
Owner

satyr commented May 20, 2011

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.

@satyr
Copy link
Owner

satyr commented May 20, 2011

Precedence could be resolved by parsing operators with the highest precedence first ...

You mean, using JS operators'? What about custom operators?

@adrusi
Copy link
Author

adrusi commented May 20, 2011

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.

@adrusi
Copy link
Author

adrusi commented May 20, 2011

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.

@satyr
Copy link
Owner

satyr commented May 20, 2011

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.

@adrusi
Copy link
Author

adrusi commented May 20, 2011

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.

@satyr
Copy link
Owner

satyr commented May 20, 2011

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

@adrusi
Copy link
Author

adrusi commented May 20, 2011

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 :)

@satyr
Copy link
Owner

satyr commented May 20, 2011

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 ;).

@adrusi
Copy link
Author

adrusi commented May 20, 2011

Ok, I'll see if I can understand the parsing going on.

@FireyFly
Copy link

FireyFly commented Jun 6, 2011

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))

@satyr
Copy link
Owner

satyr commented Jun 6, 2011

a + b ++ c <+> d to a + b["++"](c["<+>"](d))

That's actually pretty close to what we already have:

$ coco -bpe 'a + b\++ c\<+> d'
a + b['++'](c['<+>'](d));

@FireyFly
Copy link

FireyFly commented Jun 6, 2011

Oh, that's quite nice actually. I guess I'm fine with that syntax. :)

@adrusi
Copy link
Author

adrusi commented Jun 6, 2011

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

@askucher
Copy link

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants