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

Allow user-defined classes to use ++ and -- syntax. #2576

Open
lrhn opened this issue Oct 18, 2022 · 4 comments
Open

Allow user-defined classes to use ++ and -- syntax. #2576

lrhn opened this issue Oct 18, 2022 · 4 comments
Labels
request Requests to resolve a particular developer problem

Comments

@lrhn
Copy link
Member

lrhn commented Oct 18, 2022

A number class like BigInt cannot use the increment/decrement operators.

An example like BigInt x = BigInt.zero; while (something) ++x; fails complaining that int is not assignable to BigInt.
The reason for that is that ++x is defined to be equivalent to x = x + 1, and the BigInt.operator+ operator does not accept int as argument.

A similar class, from package:fixnum, is Int64. It does work with ++ because its operator+ accepts multiple argument types, including int, by having an argument type of Object.
That's bad for typing, but convenient if you want to mix integers and fixnum-types.

BigInt chose being well-typed over that convenience. In most cases that's OK, you can write x = x + BigInt.one or x += BigInt.one, but you don't get the convenience of ++/--.

@lrhn lrhn added the request Requests to resolve a particular developer problem label Oct 18, 2022
@lrhn
Copy link
Member Author

lrhn commented Oct 18, 2022

A number of fairly outrageous ideas include:

  • Allow operator+ to have a default value: BigInt operator+(BigInt other = BigInt.one) => ..., which is used for ++. (Similarly operator-(BigInt other = BIgInt.one). That's a very specialized feature, with new syntax, just to support ++/--. And you need to write both.
  • Make ++/-- use 1 as value only when int is assignable to the parameter type of operator+/operator-. Otherwise check if that type's declaration has a static constant value named one which is assignable to the type. (No new syntax, works directly with BigInt, still very special-cased, and makes one a magical name.)
  • Generalize, and allow any interface to be a special Number type, which exposes static default values for zero and one, and make them accessible to generic code bounded by Number. (Probably too close to type-classes.)

@Wdestroier
Copy link

@override
Int64 operator +(Object other) {
  Int64 o = _promote(other);
  int sum0 = _l + o._l;
  int sum1 = _m + o._m + (sum0 >> _BITS);
  int sum2 = _h + o._h + (sum1 >> _BITS);
  return Int64._masked(sum0, sum1, sum2);
}

Another alternative could be to rewrite the original implementation with union types (#1222, #83, #145)

Int64 operator +(Int64 | int other) {
  Int64 o = other is Int64 ? other : Int64(other);
  int sum0 = _l + o._l;
  int sum1 = _m + o._m + (sum0 >> _BITS);
  int sum2 = _h + o._h + (sum1 >> _BITS);
  return Int64._masked(sum0, sum1, sum2);
}

@Levi-Lesches
Copy link

Levi-Lesches commented Oct 20, 2022

A number of fairly outrageous ideas include:

How about this: x++ technically means x = x + 1, but 1 being an int is not important as far as x is concerned. Mathematically, it could be a 1.0, a BigInt.one, or any other value y such that x += y is considered "incrementing". Figuratively speaking, it's the idea of "incrementing"/"decrementing" that's important over += 1 and -= 1. So how about allowing users to override ++/-- with no parameters, so they can decide what incrementing means to their class?

void operator ++();
void operator --();

That way BigInt can do:

/// Assuming a lot about how BigInt works,
class BigInt {
  void operator ++() { this.innerValue++; }  
  void operator --() { this.innerValue--; }
}

@lrhn
Copy link
Member Author

lrhn commented Oct 20, 2022

Using operator++ gets a little ambiguous. We're used to ++ being mutating, but for BigInt, which are immutable, it needs to return the new value instead. So, it's really more like operator +1().
The operator+1 is only used by ++e and e++, which means it should probably be required to return something assignable to the declaring type, otherwise it can never be called anyway.

I think I'd rather have X operator 1() to return the value, and then let ++ and -- be defined in terms of += <one> and -= <one>.

Then we can introduce operator++ as well, as a mutating variant. If a type defines operator ++, then
++e is the same as e..operator++() and e++ is e.operator++(), the latter allowing you to return the "previous value" (if you want to, or void if you only want to allow e++ in statement position).

(C# special-cases += and -= to mean "add" and "remove" on events. We could also allow overriding those, and any other "mutating" operation, so that e += 4 can call a method to mutate the result of e directly, if such a method exists, rather than creating a new object and assign it to the existing variable. You can still do x = x + 4; if you want to farce the assignment, but a mutating += works on non-variables too.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

3 participants