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 custom classes to define their own operator behavior #8383

Open
TheCleric opened this issue Nov 9, 2023 · 9 comments
Open

Allow custom classes to define their own operator behavior #8383

TheCleric opened this issue Nov 9, 2023 · 9 comments

Comments

@TheCleric
Copy link

Describe the project you are working on

A Mandelbrot set visualization tool

Describe the problem or limitation you are having in your project

This project requires the use of complex numbers (with a real and imaginary component). Godot, understandably, does not support complex numbers, so a custom class is needed to represent them. However to do something involving math for these complex numbers (e.g., addition), I have to now write and call custom functions to do this, instead of using the built-in math operators.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

The feature enhancement would allow custom classes (such as the complex number class described above) to define their own operators so that standard mathematical (or other) operators could be supported.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

I propose this should work in a similar way to Python's current implementation. This would involve having pre-defined function names for the operators that, when implemented, would open up the ability to perform those operations, with the behavior tied to the function's implementation. Essentially, this would be "syntactic sugar" that converts certain operators to a function call, if present.

This is also in keeping with GDScript's other paradigms for class functions such as _init, _process, _draw, etc. where defining those function allow someone writing GDScript to add features to a class that are automatically recognized by the complier.

Here's a proposed example:

class_name Complex
extends Object

var r: float
var i: float

func _init(_r: float, _i: float):
  r = _r
  i = _i

func __add__(other: Complex) -> Complex:
  return Complex.new(r + other.r, i + other.i)

This would then allow you to do the simple addition in your other scripts:

var a: Complex = Complex.new(1, 1)
var b: Complex = Complex.new(2, -1)
var c: Complex = a + b

Rather than the more convoluted function call:

var a: Complex = Complex.new(1, 1)
var b: Complex = Complex.new(2, -1)
var c: Complex = a.__add__(b)

Since GDScript doesn't (to my knowledge) support function overloads, there could be a variety of these magic functions to support other types (even custom types):

class_name Complex
extends Object

var r: float
var i: float

func _init(_r: float, _i: float):
  r = _r
  i = _i

func __add__(other: Complex) -> Complex:
  return Complex.new(r + other.r, i + other.i)

func __add_int__(other: int) -> Complex:
  return Complex.new(r + other, i)

func __add_float__(other: float) -> Complex:
  return Complex.new(r + other, i)

If no function is defined for a given math operation, then it can be a variation on the current not implemented error:

print(Complex.new(0, 0) + Vector3(0, 0, 0))

Invalid operands 'Complex' and 'Vector3' in operator '+'.

If this enhancement will not be used often, can it be worked around with a few lines of script?

This enhancement would be completely opt-in, so anyone who doesn't want to use it would be unaffected.

Is there a reason why this should be core and not an add-on in the asset library?

I believe this is a core GDScript change and do not think it could be done in an asset library.

@AThousandShips
Copy link
Member

See also:

@Mickeon
Copy link

Mickeon commented Nov 14, 2023

Objects in Godot are quite hefty. Creating new Objects is relatively slow, so for these purposes I would discourage using them. But when structs are implemented into GDScript (it's just a matter of time), I feel like this would be a fitting upgrade to them.

Since GDScript doesn't (to my knowledge) support function overloads, there could be a variety of these magic functions to support other types (even custom types)

But Godot as a whole supports Variant.

func __add__(right:  Variant) -> Complex:
    if right is Complex:
         return Complex.new(r + right.r, i + right.i)

    if right is int or right is float:
         return Complex.new(r + right, i)
  
   # Probably generate an error here, too.
   return null

I'm aware it would be slower, but other things in GDScript do this as well, so it may come as expected.

@TheCleric
Copy link
Author

I realize my proposal is heavily pythonic, so if we'd rather make it in line with GDScript standards, having different function names (such as _add vs __add__) would be perfectly fine by me. I'm not married to any implementation detail as long as the spirit of the proposal is accomplished.

@aaronfranke
Copy link
Member

aaronfranke commented Nov 28, 2023

See also #279 and #7329. Operator overloading would be nice, it's a great feature in C++, C#, etc.

For your specific use case, you can use Vector2 to represent complex numbers, and it will work with addition, but you would need to define your own complex_multiply(a: Vector2, b: Vector2) method.

Or, alternatively, you can use Quaternion, and leave j and k set to 0.

Computing the Mandelbrot set is likely to be a heavy computation, so I would avoid using Object for this.

@docfail
Copy link

docfail commented Dec 22, 2023

Just want to drop in and +1 the idea of operator overloading. Certainly one could just use C++ instead but considering how many other fantastic qol features gdscript has built in, it would be a huge benefit to be able to overload at the very least basic operators, such as the mathematical operators.
Some structures one might work with such as matrices or vectors of arbitrary size(not just up to 4 components) or-as mentioned above, complex numbers-or any number of arbitrary types that might be more effectively and clearly represented in code through the use of an operator rather than a difficult-to-read function call (e.g. some addition/multiplication isn't actually transitive and the function call can make it less obvious which one is in which order unless you make it a static function).
Of course it won't be nearly as fast using objects for actual mathematical operations but there are still a lot of use cases beyond that where being able to "add" or "multiply" two objects which may have more complex functionality under the hood makes sense when you look at it.

@advent94

This comment was marked as off-topic.

@AThousandShips
Copy link
Member

@advent94 Please don't bump without contributing significant new information. Use the 👍 reaction button on the first post instead.

@sweti-yeti
Copy link

I've had a number of situations where I've built a wrapper around an existing GDScript data type (such as an array) to add specific functionality, and being able to overload arithmetic and [] for those classes to operate on the underlying data structure would have been quite welcome. .mul(), .add(), and .at() aren't terrible but the operators would help make things clean/consistent.

For my specific case of passing calls down to a member variable something like Ruby's method_missing would be helpful, though it feels like more of a stretch than asking for overloading

@Ivorforce
Copy link

Ivorforce commented Aug 30, 2024

I took a quick dive into the source code, and I don't think it should be super difficult to implement.

Dynamic Type approach (python-like)

  • Register the operators, such as the existing == for Object
  • In the operator implementation, check whether the left object implements the corresponding method (e.g. _mul(l, r)), such as the existing Object._to_string
    • Like in python, one could additionally check for right-defined operators on failure of the first case. To do this, it would simply check the right object for the existence of an _rmul(r, l) method.
  • Like in python, it would be the responsibility of the object to check the type of the other object.

Static Type approach

Alternatively, one could require registering operator overloads by explicit types, letting Godot check which overload should be used. This could be a bit more verbose but solves the _rmul duplication.

I agree that this would be undesirable for many cases where operators are usually overloaded, due to objects being kind of slow, but it wouldn't be any worse than any normal object calls.

I'm not sure how proposals like this are usually decided on by the Godot community, but I'm willing to take a crack at it if it's wanted and has a chance of being merged.

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

No branches or pull requests

8 participants