Skip to main content
GameDev.net gamedev.net
🔒 Locked

Limit Operator Overloading?

Started by Telastyn Feb 20, 2009 at 2:08 PM 11 replies 3.2k views
Original Post
Telastyn
Telastyn
As some might know, I dabble around with a progamming language. And I would like community thoughts about operator overloading. Function declarations in the language are a little more free form than in some languages. Normal C-ish looking methods are there:

f() => void {...}                 // f()
f(int x) => int {...}             // f(5)
f(int x, int y) => bool {...}     // f(5,5) 
So are functional-ish looking methods:

f (int x) (int y) => bool {...}     // f 5 5
So are infix operators:

(int x) op (int y) => bool {...}    // 5 op 5
And the language allows such constructs arbitrarily (except one of the first two bits must be a simple identifier):

(int x) foo (int y) bar (int z) => void {...}       // 5 foo 6 bar 7
foo bar => void {...}                               // foo bar
So, it would be useful to allow symbols in place of common identifiers:

(int x) + (int y) => int {...}        // 2+2
++(int x) => int {...}                // ++x
What do you think the limitation on this should be? ++(int x) is tricky for example. Should ++ be parsed out as its own operator, or should the individual symbols be parsed out separately and behave like foo foo (int x)? If they're parsed out individually, should the language limit chaining; that is prevent someone from making the operator !%^+++*&^? If they're not parsed out individually, then I'd need to make some list of overloadable operators. What should be included there then? Or is operator overloading evil incarnate and I should forget the whole thing?
popsoftheyear
popsoftheyear
[opinion]
I would think the more freedom you give a user, the more likely they are to mess things up. For instance, allowing a user to define their own operators is almost surely asking for trouble. Then again, I'm not experienced with functional languages, and maybe this would be an aid in that kind of paradigm. Procedurally or OOP - wise, this would wreak havok trying to find out where in your huge code base you defined operator @$$$!+! and what you were thinking it was supposed to do.

Operator overloading itself seems like a must though, but I'd take it a step further than c++ and force a very specific syntax for each operator. I'm not sure about how the rest of your language works and implicit conversions and what not but that could be asking for big troubles...
[/opinion]

Also I was wondering, if I had a line
f(int x) => bool { ... }

How does your language know I meant it to be a C-ish declaration vs a functional-ish declaration? Is it space-sensitive so that the above is completely different from
f (int x) => bool { ... }


Cheers
-Scott
visitor
visitor
++operator seems a bit like a mis-feature of C (there's a corresponding assembly instruction, that's why it was invented?) to carry over. += 1 is not too inconvenient to type, IMO.

But what endless fun you can have with i = i++ + ++i; :) Even if you define the order of evaluation it will still make your head explode. (Or even without chaining n[x++] = x; )
Cornstalks
Cornstalks
Why do you want to let the user redefine operators for primitive data types? And why must the user define the order or operations? It seems very counter-intuitive.
DevFred
DevFred
Quote:
Original post by visitor
But what endless fun you can have with i = i++ + ++i; :)
Even if you define the order of evaluation it will still make your head explode.

Because it's still undefined, even if you can rely on it being evaluated from left to right - there are no sequence points between the accesses to i.
dascandy
dascandy
Quote:
Original post by popsoftheyear
[opinion]
I would think the more freedom you give a user, the more likely they are to mess things up. For instance, allowing a user to define their own operators is almost surely asking for trouble. Then again, I'm not experienced with functional languages, and maybe this would be an aid in that kind of paradigm. Procedurally or OOP - wise, this would wreak havok trying to find out where in your huge code base you defined operator @$$$!+! and what you were thinking it was supposed to do.

Operator overloading itself seems like a must though, but I'd take it a step further than c++ and force a very specific syntax for each operator. I'm not sure about how the rest of your language works and implicit conversions and what not but that could be asking for big troubles...
[/opinion]


For me too, opinion.

[opinion]
I think that you should give programmers and developers all the freedom you can give them. That does not mean that I agree with your idea verbatim.

Give people everything that adds something useful not gotten in another way, that is in line with your language. If the abstract thinking doesn't work out well, use it pragmatically and add a feature when somebody comes up with a good and practical use case. For example, I can think of a use for about 30 kilometer (20 miles) of rope, but I'm sure I won't use it for that because it's a bit preposterous. Just give me enough to do whatever I want to do, not more. Freedom comes from being able to do anything you want. Feeling of being lost is from being able to do more, and not knowing how to do what you want.

[/opinion]

Sorry about the unreadability of that bit of text.
rip-off
rip-off
Quote:

Or is operator overloading evil incarnate and I should forget the whole thing?

I don't believe so. I think that there are cases where operator overloading is a really nice feature.

I believe that overloading operators on primitives is an interesting idea. Consider if we could overload operators for primitives - we could do things like disable detect (at compile or runtime, depending) exact float comparison. Given some time I could probably think of other examples where overloading operators on primitives is useful. I do of course see the potential for great evil, so I don't really have a concrete opinion on whether you should leave it in.

I would think that restricting the allowed set of operators to the set your language provides makes sense. If people want to make "new" operators, your language already allows it via phrases. So they could use "inc" instead of ++, which makes more sense to me (unless operator ++ is a built-in in your language.

I think it is best to put the decision in the hands of the developers.

For example, I dislike Java's lack of consistency. Apparently it was sensible to allow operator+ with strings objects, but no support is provided for operator ==, which is far more universal. Making operator== equivalent to .equals() and providing some other method of doing exact reference comparisons would have been better, I think.
visitor
visitor
Quote:

Because it's still undefined, even if you can rely on it being evaluated from left to right - there are no sequence points between the accesses to i.


I know that. Even if you define it strictly (e.g side-effects will be applied immediately) it seems that different people might still interpret these things differently.

E.g if this wasn't undefined:

n[i++] = i;


it seems that some people naturally read this from left to right and other from right to left. It would still be a source of confusion.

Perhaps you might restrict how it can be used (once per expression, variable in question cannot appear any more in the expression) but I personally wouldn't miss it much in a language that doesn't have it. For simple counting += 1 is enough, and loops hopefully will use higher-level syntax where you don't worry about incrementing loop counters :)
Telastyn
Telastyn
Quote:
Original post by MikeTacular
Why do you want to let the user redefine operators for primitive data types?


They won't be able to; just an example. You'd need to use user defined types for (foo x) + (foo y)

Quote:

And why must the user define the order or operations?


The user doesn't define order of operations. The user can define a block of code as a series of tokens. The series itself has a defined order, but a statement is not limited to a single series. A phrase with two operators/keywords is like the ternary operator where the ?: must be dealt with together.


Quote:

How does your language know I meant it to be a C-ish declaration vs a functional-ish declaration?


Parens are only used for grouping. f(int x) => int may be invoked as f(5) or f 5

Whitespace is not syntactically significant.
Zahlman
Zahlman
I'm actually currently designing my language around a paradigm where, among other things, functions are simulated by operator overloading. :) (Function calls work by overloading the 'epsilon' operator - a binary operator represented by a zero-length string - with a proxy of some kind on the left-hand side to give a name to the function; you can only have one-argument functions this way, but since you can easily create anonymous structures, there's no need for more.) A lot of the ideas come from trying to refactor Python's underlying models of stuff and get rid of redundant "ways to do it" (despite the schism from Perl, in many ways they missed huge opportunities IMO ;) ).
Cornstalks
Cornstalks
Quote:
Original post by Telastyn
Quote:
Original post by MikeTacular
Why do you want to let the user redefine operators for primitive data types?


They won't be able to; just an example. You'd need to use user defined types for (foo x) + (foo y)

Okay, good. That makes more sense.

Quote:
Original post by Telastyn
Quote:
Original post by MikeTacular
And why must the user define the order or operations?


The user doesn't define order of operations. The user can define a block of code as a series of tokens. The series itself has a defined order, but a statement is not limited to a single series. A phrase with two operators/keywords is like the ternary operator where the ?: must be dealt with together.

Hmm... I must be missing something because I'm still confused about why you have a section in your FAQ titled "Why does 2+4*2 give me 12, and not 10?".

I personally think you should treat ++ as its own operator. Letting the user create his/her own operators seems like it could easily lead to some very confusing code. Programmers are usually sane enough to overload operators in a sensible way such that just looking at a single line of code can give a person a decently accurate guess as to what the operator does. However, one reason this works is because (in languages like C++) there are a set number of operators that programmers become familiar with, so it's easier to guess what an overloaded operator will do if the programmer knows what that operator will do in a trivial, unoverloaded example. When you allow programmers to throw new operators in, other programmers must learn what each new operator does without having the trivial unoverloaded examples that the built in operators have.
Telastyn
Telastyn
Quote:
Original post by MikeTacular
I personally think you should treat ++ as its own operator.


Okay cool.

Quote:

Letting the user create his/her own operators seems like it could easily lead to some very confusing code. Programmers are usually sane enough to overload operators in a sensible way such that just looking at a single line of code can give a person a decently accurate guess as to what the operator does.


Yup, usually. And confusing code could easily come from custom operators.

Quote:

However, one reason this works is because (in languages like C++) there are a set number of operators that programmers become familiar with, so it's easier to guess what an overloaded operator will do if the programmer knows what that operator will do in a trivial, unoverloaded example.


Correct. Except those cases in C++ where an operator is extraneously overloaded ("because you can") or overloaded in-exactly because the operator you want isn't available (boost::spirit for example).

Quote:

When you allow programmers to throw new operators in, other programmers must learn what each new operator does without having the trivial unoverloaded examples that the built in operators have.


True. The main use case for perhaps non-standard operators (or symbol combinations rather) is for DSL usage.

The language is a bit like Scala, in that it aims to allow the programmer to adapt the language towards the problem domain. Having infix functions and the phrase support (the 'arbitrary length' description above) allows code that uses the terms and symbols of the problem domain like a domain specific language, without going through the effort of actually making one.

In an ideal scenario, the operator used would be known to people within that domain even if I don't know about it, or if it doesn't get widespread use. The goal being to avoid stuff like boost::spirit (or others) that overload an operator that doesn't quite mean the same thing.

The question comes then if that's sufficient benefit compared to the inevitable misuse. (or if perhaps the misuse might be lessened by infix methods or the availability of more unique symbols).


Quote:

Hmm... I must be missing something because I'm still confused about why you have a section in your FAQ titled "Why does 2+4*2 give me 12, and not 10?".


Something perhaps outside the discussion, but since the thread seems to be dying down...

That section is there because the language doesn't hard code order of operations. With user definable infix functions/operators, order of operations becomes a problem. Most languages I've seen that allow it do a priority system where a method is given a number to say when it should go, or require parens everywhere. Parens everywhere suck. And placing a number for the method is kinda arbitrary. You can't know what weights other people have given to their methods, or would want you to give to yours.

So Tangent infers it. It is essentially the opposite end of the spectrum from type inference. Instead of knowing what the types of tokens are (except 1), and the operations involved the language knows the types of the tokens involved and determines what order they need to be invoked to make sense. 'Make sense' to Tangent is that the statement results in void. A few extra rules/preferences are added to reduce ambiguity, but that's how it works.

A quick example. Assume you have these tokens (with A->B being a function that takes A and returns B, and op before it means it can be used infix):

intop (int,int)->voidintop (int,int)->intstring->intstring


A token stream for something like this would be generated for something like:
int x = 3 + strlen("How now brown cow?");


The order of operation inference will see that the only way to make sense of that is to do the strlen first, the addition second and the assignment last. It allows arbitrary infix functions/operators without the abundance of parens or user-assigned weights. It also allows you to remove the parens which is necessary for (non-ugly) partial application and certain trickery like the phrases mentioned above.


It falls down when doing math though since it relies on type info. Since an int is an int is an int be it multiplied or added, there's no way to codify a preference. And with C# interop being an important desire, it's not really plausible to jury-rig something where there are a few different int types to cause the order of operations to fall out (like is done in parsers now to get order of operations working).

Anyways, that's why it's in the FAQ. Because order of operations for math is not what people would expect at the moment.
Hodgman
Hodgman
Quote:
Original post by Telastyn
What do you think the limitation on this should be? Should the language limit chaining; that is prevent someone from making the operator !%^+++*&^?
If your users want to write incredibly obfuscated code, then it's not your problem ;)

Seriously though, the &#106avascript community's invention of the $ and $$ functions was one of the best things to happen to that language, and plenty of great C++ idioms came out of unexpected use of the language's features. So, I'd say you should impose the least amount of arbitrary restrictions as possible.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.