#11 Operators to Methods

brian Sun 11 Dec 2005

I am thinking is that most operators just map to a specific method name. If you implement a method with that name, then in essence you can call it with an operator shortcut:

a + b    -> a.plus(b)
a - b    -> a.minus(b)
a * b    -> a.multiply(b)
a / b    -> a.divide(b)
a % b    -> a.modulus(b)
a[b]     -> a.slice(b)
a[b] = c -> a.splice(b, c)
a << b   -> a.lshift(b)
a >> b   -> a.rshift(b)
a & b    -> a.and(b)
a | b    -> a.or(b)
a ^ b    -> a.xor(b)
~a       -> a.not()
-a       -> a.negate()
a == b   -> a.equals(b)
a != b   -> ! a.equals(b)
a <=>    -> a.compare(b)
a > b    -> a.compare(b) > 0
a >= b   -> a.compare(b) >= 0
a < b    -> a.compare(b) < 0
a <= b   -> a.compare(b) <= 0

Remember that aliased classes implement their methods statically, but Int/Real will actually declare their operators as above. It is just that the compiler will optimize these calls into direct bytecode.

Special operators like such as && || ! ++ -- will remain non-OO, driven solely by the compiler.

Do you guys like that? Any other names or operators to suggest?

andy Sun 11 Dec 2005

That makes sense to me.

john Mon 12 Dec 2005

If we're going to include bitwise operators you should include complement:

~a = a.not()

I think having standard methods for operator overloading is a great idea.

brian Mon 12 Dec 2005

Yeap, missed that one (I added to the original list)

So you are cool with this design before I charge?

john Mon 12 Dec 2005

I like it a lot. Slice and splice are especially intriguing.

john Mon 12 Dec 2005

Make sure you doc the required consistency for compare() and slice()/splice().

brian Tue 20 Dec 2005

I've checked in the code to implement the full set of operator overloading as methods defined above (with the exception of splice). It's cool because now you can do natural things like:

"a" < "b"
"a" == "b"

I added new support for the === (reference equality) and <=> (comparision) operators.

In the AST, UnaryExpr and BinaryExpr represent "built-in" operators which cannot be overloaded including: boolean not, and, and or; ===, and numeric negative.

ShortcutExpr represents unary, binary, and tertiary (splice) operators which can be overloaded via a method with the appropiate name. My assembler code optimzes just about everything for int and real. Plus I handle string addition specially to only create one StringBuilder if possible.

The next thing I want to do is rework the assignment operators like += to use operator overloading (or if becomes ugly, I might just force those operators to only be used with numeric types).

brian Wed 21 Dec 2005

I finished this work up so that the compound assignment operators (like +=) work with the standard shortcut methods. So now you can do things like this:

TimeSpan x := 5ns;
x += 2ns;
x *= 2;

Which really compiles to this:

TimeSpan x = TimeSpan.make(5);
x = x.plus(TimeSpan.make(2));
x = x.multiply(2.0)

tompalmer Sat 14 Jun 2008

I couldn't find a more recent discussion of this topic. I hope I didn't just miss it. But my comment is that I like method names that say the meaning, for instance multiply or times instead of star. My concern is that if people think of it as star, they may be more likely to use it creatively. And creative operator overloading is one of the sad things for C++ (and Scala). Makes code hard to understand. This question applies to other operators (slash, percent, ...) as well.

Anyway, I've made enough comments for one day. And I don't mean to be too negative. I'm here in the first place because I'm intrigued by a lot of the good things in Fan. (And I also understand that a lot of things come down to opinion. There are pros and cons to many things.)

cbeust Sat 14 Jun 2008

Brian,

This looks good but I'm a bit concerned about the names slice and splice, which don't mean the same thing to me (maybe due to the fact that I'm new to Fan?).

The two operators seem to map to verbs like set and get, although these are already taken. Any other suggestions?

-- Cedric

helium Sat 14 Jun 2008

I couldn't find a more recent discussion of this topic.

http://www.fandev.org/sidewalk/topic/206

tompalmer Sat 14 Jun 2008

Thanks for the pointer.

andy Sat 14 Jun 2008

Cedric, slice is used because Fan supports Ranges (a la Ruby):

"abcdef"[2..4] => "cde"

brian Mon 16 Jun 2008

I don't have a strong opinion on how we map operators to names. Although I do want a mapping to simple alpha names to support easy reflection and use in Java and C# code. Operator overloading always has the potential for abuse, but in Fan those operators are always just a shortcut for their corresponding method.

If we change them, then an obvious choice would be to reuse the names used by Groovy:

Operator   Fan            Groovy
a + b      a.plus(b)      a.plus(b)
a - b      a.minus(b)     a.minus(b)
a * b      a.star(b)      a.multiply(b)
a / b      a.slash(b)     a.div(b)
a % b      a.percent(b)   a.mod(b)
a | b      a.pipe(b)      a.or(b)
a & b      a.amp(b)       a.and(b)
a ^ b      a.caret(b)     a.xor(b)
~a         a.tilde()      a.bitwiseNegate()
-a         a.negate()     a.negative()

If anyone has strong opinions about switching (or other suggestions), please comment.

tompalmer Mon 16 Jun 2008

I recommend following Groovy conventions as far as possible. Side note, operators are the one place where static overloading works well (or, I guess, dynamic multimethods might work, too if well optimized well, but I don't want to go down any rabbit holes). For instance, I might want to multiply a matrix by a float or another matrix, or I might want to do this:

fansh> n := 1.0 + 1
ERROR(6): Invalid args plus(sys::Decimal), not (sys::Int)

I recommend one of three options:

  1. Live with it. It won't kill anyone.
  2. Allow optional annotations like @operator("*").
  3. Provide naming conventions like Float plusInt(Int i) where it checks for either plus or plus<BaseName>. For this case, you'd use getRange rather than slice, because it follows this use-case (overloading) and naming convention.

But in any case, I personally think following Groovy method names (mostly) for the common case is the best idea. Maybe get is still better than Groovy's getAt or other slight deviations.

helium Mon 16 Jun 2008

Ahm...

I recommend following Groovy conventions as far as possible.

Where is the "because ... " part of your argumentation? Why do you recomend it? Because you personally like Groovy? Or does it have any technical reasons?

Examples of different languages:

Fan:

a.star(b)

C++:

a.operator *(b)

D:

a.opMul(b)

Groovy:

a.multiply(b)

Haskell:

a * b

cbeust Mon 16 Jun 2008

Thanks for the overview, Helium.

For consistency, I would probably lean toward the C++ syntax (I can't believe I just said that) for a couple of reasons:

  • No need to memorize the correspondence between the operator and its method
  • Makes it easier to identify the operator redefinitions inside a class (good for both the source code and the documentation)

Could the Fan parser accomodate the C++ syntax? (or something similar, maybe use an underscore instead of a space, since Fan seems to treat them similarly?)

-- Cedric

andy Mon 16 Jun 2008

The C++ syntax won't really work. Remember that using the operator is optional, so the slot name has to be a proper identifier name:

4.plus(3) => 7
4 + 3     => 7

tompalmer Mon 16 Jun 2008

I don't use Groovy, but I think it's ideas on operator overloading are the best out there. It creates a correspondence between the symbol and what it means. And it looks nice, too. Fan seemed to be using a similar style (which I liked), but I thought that Groovy's semantic names were better, and why be too unique when someone with a similar style already has conventions.

As for examples, Python is worth listing, too:

def __mul__(self, other): ...

And Eiffel, too (and I think different versions have some variation here):

multiply alias "*" (other: INTEGER): INTEGER

I like a readable method name, and I generally prefer convention over configuration.

Also, I think I changed my mind on get vs. getAt. I think getAt scales better. And my naming-convention-overloading style would then use getAtRange rather than slice.

alexlamsl Tue 17 Jun 2008

As for the static overloading point, I think it is pretty much a non-issue with a new language like Fan. If you want both

1.0 + 2

and

1.0 + 2.0

Wouldn't a method like plus(Number) suffice?

brian Wed 18 Jun 2008

This discussion has branched into two discussions. I will create a new topic to address the problem of numeric coercion.

Regarding the operator to method names, I am mostly in agreement with Tom. I want to keep operators mapped to simple readable names. This makes the API easier to use in Java and C#. I think in retrospect the Groovy names are probably better since they might do a better job at curbing operator overload abuse - I guess we are all scarred from C++ (in more ways than one :).

I'm not inclined to change all of the mappings, only the mathematical and bitwise operators I listed above.

helium Wed 18 Jun 2008

I guess we are all scarred from C++

No I'm not. C++ was the first realy popular language that allowed users to overload operators so many people experimented with it and not all experiments resulted in something usefull. (Another problem of C++ was that it has only a limited set of operators so you had to reuse the existing operators for new ideas.)

Anyway, Groovy isn't very consisten, is it? "multiply" and "div"? Why not "multiply" and "devide" or "mul" and "div"?

@tompalmer: I don't like "getAtRange". What do you think about "getSlice"?

tompalmer Wed 18 Jun 2008

The class name is Range. My naming convention proposal just adds the class name to the base method name. Another alternative might be to just use "starts with" logic so anything starting with get (or getAt) is considered an accessor for []. But the other thread Brian is more along these subjects, so I'll head over there.

brian Wed 25 Jun 2008

I'm probably going to make this change for this week's build. Here is my final proposal:

Operator   Fan Current    Groovy              Fan Proposed
a + b      a.plus(b)      a.plus(b)           - 
a - b      a.minus(b)     a.minus(b)          -
a * b      a.star(b)      a.multiply(b)       a.mult(b)
a / b      a.slash(b)     a.div(b)            a.div(b)
a % b      a.percent(b)   a.mod(b)            a.mod(b)
a | b      a.pipe(b)      a.or(b)             a.or(b)
a & b      a.amp(b)       a.and(b)            a.and(b)
a ^ b      a.caret(b)     a.xor(b)            a.xor(b)
~a         a.tilde()      a.bitwiseNegate()   a.not()
-a         a.negate()     a.negative()        -
a++        a.increment()  a.next()            -
a--        a.decrement    a.previous()        -
a << b     a.lshift()     a.leftShift(b)      -
a >> b     a.rshift()     a.rightShift(b)     -

Those marked as - remain the same. I don't like the way Groovy isn't consistent between multiply and div. Fan tends towards the abbreviated names so I'm going to use those.

andy Wed 25 Jun 2008

I like those.

cbeust Wed 25 Jun 2008

This looks good. "mult" stands out a bit, though, since most of the others are not abbreviated. How about "multiply"? It's not that much longer.

tompalmer Wed 25 Jun 2008

I think mult sound more Fan-like (where we also have Int, Str, Obj, and sys). Either mult and div, or multiply and divide. So I guess I'm fine departing from Groovy. And I guess if or and and are OK, then not should be, too, even if I also get why they were more explicit in Groovy.

Overall, I like the recommendations.

brian Wed 25 Jun 2008

actually I think I'm going to make bitwise not inverse():

~a         a.tilde()      a.bitwiseNegate()   a.not()

The seems as generally accurate as not, but not to be confused with the ! operator

alexlamsl Thu 26 Jun 2008

I would vote for inverse as well; while not might be equivalent inverse when there is only one "bit" to consider, once we go beyond the latter would seem to fit the definition of ~ more.

Login or Signup to reply.