You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The only issue that I could find about operator overloading currently #19770, although it's currently closed and doesn't have many details.
Goal
Operator overloading should be used to create datatypes that represent things that already exist in Go. They should not represent anything else, and should ideally have no other function.
New operators should not be allowed to defined, we should only be able to define currently existing operators. If you look at languages that let you define your own operators (Scala, looking at you!) the code becomes extremely messy and hard to read. It almost requires an IDE because operator overloading is very heavily used.
Using an operator for anything other than it's purpose is a bad idea. We should not be making data structures with fancy looking + operators to add things to the structure. Overloading the + operator in Go should only be used for defining addition/concatenation operators for non-builtin types. Also, as per Go spec, binary operators should only be used for operating on two values of the same type.
Operators should only operate on and return a single type. This keeps things consistent with how operators currently work in Go. We shouldn't allow any type1 + string -> type1 stuff.
Operators should only be defined in the same package as the type they are defined on. Same rule as methods. You can't define methods for structs outside your package, and you shouldn't be able to do this with operators either.
And last but not least, operators should never mutate their operands. This should be a contract that should be listed in the Go spec. This makes operator functions predictable, which is how they should be.
Unary operators should not need to be overloaded.
+x is 0 + x
-x negation is 0 - x
^x bitwise complement is m ^ x with m = "all bits set to 1" for unsigned x
and m = -1 for signed x
This part of the spec should always remain true, and should also remain true for anything using these operators. Perhaps ^x may need to have it's own, as there's no good way to define "all bits set to 1" for an arbitrary type, although defining a .Invert() function is no less readable IMO.
Unary operations on structs would then therefore be Type{} + t or Type{} - t, and pointers would be nil + t and nil - t. These may have to be special cases in the implementation of operator functions on pointers to types.
Assignment operators should also never be overloaded.
An assignment operation x op= y where op is a binary arithmetic operator is equivalent to x = x op (y) but evaluates x only once. The op= construct is a single token.
This should remain the same just as unary operators.
If we do not permit overloading the ^x unary operator, this means that we only need to define binary operations.
// A modular integer.
type Int struct {
Val int
Mod int
}
// ==============
// each of the functions would have the following function body:
//
// if a == Int{} { // handle unary +
// a.Mod = b.Mod
// }
//
// checkMod(a, b)
// nextInt = Int{Val: a.Val + b.Val, Mod: a.Mod}
// nextInt.Reduce()
//
// return nextInt
//
// ==============
// In all of these, it would result in a compile error if the types of the arguments
// do not match each other and the return type.
// My new favorite. This makes for a simple grammar. It allows
// people who prefer function calls can instead use the `Add` function.
operator (Int + Int) Add
func Add(a, b Int) Int { ... }
// My old favorite. Abandoning the `func` construct clarifies
// that these should not be used like a standard function, and is much
// more clear that all arguments and the return type must be equal.
op(Int) (a + b) { ... }
operator(Int) (a + b) { ... } // <- I like this better
// My old second favorite, although this looks a lot like a standard method definition.
// Maybe a good thing?
func (a + b Int) Int { ... }
// It can be fixed with adding an "op" to signify it's an operator function, although
// I do not like it because it just reads wrong. Also, looks like we're defining a package-level
// function named `op`... which is not what we are doing.
func op (a + b Int) Int { ... }
// Although at the same time, I don't like having words
// before the `func`... I feel that all function declarations should begin with `func`
op func (a + b Int) Int { ... }
// Another idea could just be to define a method named "Plus", although this
// would cause confusion between functions like `big.Int.Plus` vs `big.Int.Add`.
// We probably need to preserve `big.Int.Add` for microoptimization purposes.
func (a Int) Plus(b Int) Int { ... }
Considering other languages' implementations.
C++
// there's several ways to declare, but we'll use this one
Type operator+(Type a, Type b)
I think C++ isn't a bad language, but there are a lot of new programmers who use it and think it's "super cool" to implement operator functions for everything they make, including stuff like overloading the = operator (which I have seen before).
I also have a couple friends from college who really enjoyed defining operator functions for everything... no bueno.
It gives too much power to the programmer to do everything that they want to do. Doing this creates messy code.
Swift
static func +(a: Type, b: Type) -> Type
Note that custom operators may be defined, and you can define stuff like the operator's precedence. I have not looked much into how these operators end up being used, though.
C#
public static Type operator+ (Type a, Type b)
Operator functions in C# end up being massively overused in my experience. People define all of the operators for all of their data structures. Might just be a consequence of using a language with many features, though.
Kotlin
operator fun plus(b: Type): Type // use "this" for left hand side
Operator functions get used everywhere, and even the standard library is littered with them. Using them leads to unreadable code. For instance, what does mutableList + elem mean? Does it mutate mutableList? Does it return a new MutableList instance? No one knows without looking at the documentation.
Also, defining it as a method instead of a separate function just begs it to mutate this. We do not want to encourage this.
Open Questions
Which operators should be allowed to be overridden?
So, the mathematical operators +, -, *, /, % are pretty clear that if this gets implemented, we'd want to overload these operators.
List of remaining operators that should be considered for overloading:
<< and >> (I do not think this is a good idea)
|, &, and &^ (also do not think this is a good idea)
<, >, <=, >=, ==, != (maybe a good idea?)
If we include <, we should include == to prevent the confusing case of x <= y && y >= x but x != y.
Overloading equality may be a good thing. big.Int suffers because the only way to test equality is with a.Cmp(b) == 0 which is not readable at all.
I have left out || and && because they should be reserved exclusively for bool or types based on bool (has anyone ever even based a type on bool?) and see no reason to override them.
Should we even allow operator overloading on pointer types?
Allowing operator overloading on a pointer type means the possibility of mutating, which we do not want. On the other hand, allowing pointer types means less copying, especially for large structures such as matrices. This question would be resolved if the read only types proposal is accepted.
Disallowing pointer types
Does not allow mutation
No need for nil checks in operator implementation
Allowing pointer types
Allows operators to be consistent with the rest of the type's methods.
ie *big.Int is *big.Int everywhere else, it would be good for consistiency
Since it's consistent, it makes it easier to pass into other functions.
ie Can't pass big.Int into a function that takes *big.Int
Perhaps it should be a compile-time error to mutate a pointer in an operator function. If read-only types were added then we could require the parameters to be read-only.
Should it reference/dereference as needed?
Methods currently do this with their receivers. For instance:
// Because the receiver for `NewInt` is `*big.Int`,
// the second line is equivalent to `(&num).NewInt(....)`
var num big.Int
num.NewInt(5000000000000000000)
So should the same logic apply to operator functions?
I'm aware that this will probably not be added to Go 2, but I figured it would be a good thing to make an issue for, since the current issue for Operator Functions is quite small and, well, it's closed.
Changed examples to use a modular integer rather than a *big.Int since the math/big package is designed to be used in an efficient way, and added that read-only types would benefit this proposal
The only issue that I could find about operator overloading currently #19770, although it's currently closed and doesn't have many details.
Goal
Operator overloading should be used to create datatypes that represent things that already exist in Go. They should not represent anything else, and should ideally have no other function.
New operators should not be allowed to defined, we should only be able to define currently existing operators. If you look at languages that let you define your own operators (Scala, looking at you!) the code becomes extremely messy and hard to read. It almost requires an IDE because operator overloading is very heavily used.
Using an operator for anything other than it's purpose is a bad idea. We should not be making data structures with fancy looking
+operators to add things to the structure. Overloading the+operator in Go should only be used for defining addition/concatenation operators for non-builtin types. Also, as per Go spec, binary operators should only be used for operating on two values of the same type.Operators should only operate on and return a single type. This keeps things consistent with how operators currently work in Go. We shouldn't allow any
type1 + string -> type1stuff.Operators should only be defined in the same package as the type they are defined on. Same rule as methods. You can't define methods for structs outside your package, and you shouldn't be able to do this with operators either.
And last but not least, operators should never mutate their operands. This should be a contract that should be listed in the Go spec. This makes operator functions predictable, which is how they should be.
Unary operators should not need to be overloaded.
This part of the spec should always remain true, and should also remain true for anything using these operators. Perhaps
^xmay need to have it's own, as there's no good way to define "all bits set to 1" for an arbitrary type, although defining a.Invert()function is no less readable IMO.Unary operations on structs would then therefore be
Type{} + torType{} - t, and pointers would benil + tandnil - t. These may have to be special cases in the implementation of operator functions on pointers to types.Assignment operators should also never be overloaded.
This should remain the same just as unary operators.
If we do not permit overloading the
^xunary operator, this means that we only need to define binary operations.Issues/Projects aided by operator overloading
#19787 - Decimal floating point (IEEE 754-2008)
#26699 - Same proposal, more detail
#19623 - Changing
intto be arbitrary precision#9455 - Adding
int128anduint128this code - Seriously it's gross
really anything that uses math/big that isn't micro-optimized
If I went searching for longer, there'd probably be a few more that pop up
Syntax
What's a proposal without proposed syntaxes?
Considering other languages' implementations.
C++
I think C++ isn't a bad language, but there are a lot of new programmers who use it and think it's "super cool" to implement operator functions for everything they make, including stuff like overloading the
=operator (which I have seen before).I also have a couple friends from college who really enjoyed defining operator functions for everything... no bueno.
It gives too much power to the programmer to do everything that they want to do. Doing this creates messy code.
Swift
Note that custom operators may be defined, and you can define stuff like the operator's precedence. I have not looked much into how these operators end up being used, though.
C#
Operator functions in C# end up being massively overused in my experience. People define all of the operators for all of their data structures. Might just be a consequence of using a language with many features, though.
Kotlin
https://kotlinlang.org/docs/reference/operator-overloading.html, actually a really nice read.
Operator functions get used everywhere, and even the standard library is littered with them. Using them leads to unreadable code. For instance, what does
mutableList + elemmean? Does it mutatemutableList? Does it return a newMutableListinstance? No one knows without looking at the documentation.Also, defining it as a method instead of a separate function just begs it to mutate
this. We do not want to encourage this.Open Questions
Which operators should be allowed to be overridden?
So, the mathematical operators
+,-,*,/,%are pretty clear that if this gets implemented, we'd want to overload these operators.List of remaining operators that should be considered for overloading:
<<and>>(I do not think this is a good idea)|,&, and&^(also do not think this is a good idea)<,>,<=,>=,==,!=(maybe a good idea?)<, we should include==to prevent the confusing case ofx <= y && y >= xbutx != y.Overloading equality may be a good thing.
big.Intsuffers because the only way to test equality is witha.Cmp(b) == 0which is not readable at all.I have left out
||and&&because they should be reserved exclusively forboolor types based onbool(has anyone ever even based a type onbool?) and see no reason to override them.Should we even allow operator overloading on pointer types?
Allowing operator overloading on a pointer type means the possibility of mutating, which we do not want. On the other hand, allowing pointer types means less copying, especially for large structures such as matrices. This question would be resolved if the read only types proposal is accepted.
Disallowing pointer types
nilchecks in operator implementationAllowing pointer types
*big.Intis*big.Inteverywhere else, it would be good for consistiencybig.Intinto a function that takes*big.IntPerhaps it should be a compile-time error to mutate a pointer in an operator function. If read-only types were added then we could require the parameters to be read-only.
Should it reference/dereference as needed?
Methods currently do this with their receivers. For instance:
So should the same logic apply to operator functions?
I'm aware that this will probably not be added to Go 2, but I figured it would be a good thing to make an issue for, since the current issue for Operator Functions is quite small and, well, it's closed.
Edits:
*big.Intsince themath/bigpackage is designed to be used in an efficient way, and added that read-only types would benefit this proposal