GNOME Bugzilla – Bug 518604
Implementing operators
Last modified: 2018-05-22 13:04:01 UTC
(This is more or less what Jürg, Raffaele and me discussed while taking a bus from FOSDEM to the hotel) Sometimes it is really convenient to overwrite mathematical operators likes it is done in C++. Also the operators "=" and "==" can save quite a couple of code and make it more readable. The idea is to create special interfaces that automaticly map certain method names on operators: public interface Vala.Compare { public abstract compare(Vala.Compare other); } which would then map to the operator "==" and of course both objects have to be of the same type to be compared. Of course this is for Vala 2.x!
Confirming. We should discuss how to do such a thing in detail. Jürg and I discussed the concept of static interfaces, which could help achieve this goal.
I don't think we really need to use interfaces for this. I've implemented support for `in' operators in custom types by just checking for a `contains' member method with an appropriate signature. Something similar is used to support `foreach' when the type provides an `iterator' method. I think we should proceed in that direction by supporting at least `compare', `equal', and `hash' (not as operator but as implicit argument for hash table creation). However, I'm considering to support this only for structs to avoid a conflict between `equal' and reference-type equality checking. Operator support makes a lot more sense for value types than for reference types, anyway, and we want to limit possible abuse of custom operator implementations - and hence not providing full operator overloading.
2009-01-16 Jürg Billeter <j@bitron.ch> * vala/valaassignment.vala: * vala/valaelementaccess.vala: * vala/valasemanticanalyzer.vala: * gobject/valaccodearraymodule.vala: * gobject/valaccodeassignmentmodule.vala: * gobject/valaccodebasemodule.vala: Do not require libgee to support element access in custom types
I think that using Attributes to specify which operator a method maps to might be a good way of doing this, for example: class Complex { [Operator name="+"] Complex plus(Complex c) [Operator name="=="] bool equals(Complex c) ... } class MyStringClass { [Operator name="+"] MyStringClass concatenate(MyStringClass c) [Operator name="+"] MyStringClass concatenate_cstring(string c) ... } Doing that would make Vala operators purely syntactic sugar over the underlying C methods, and allow the methods to have different names where it makes sense (like "plus" and "concatenate" above). It would also allow you to nicely wrap an external library which contains collection-like classes whose methods aren't named exactly the same as libgee's (which Vala treats specially for the "in" operator and foreach).
I'd just like to point out that the lack of operator overloading is a major issue in Java. BigDecimal is night-unusable because of this. On the other hand, the solution in C++ or C# makes parsing much more difficult than it should be, so it's not ideal either. Python has "protocols" instead of interfaces, so classes can implement __add__ if they want +, but they don't need to implement anything else. The closest thing to this in Vala would be James's example with Attributes.
*** Bug 726205 has been marked as a duplicate of this bug. ***
Created attachment 271687 [details] [review] Patch enabling operator overloading for arithmetic operators I implemented operator overloading in vala for the 4 arithmetic operations +, -, *, /. To declare, that a method overloads an operator is use an attribute like '[Semantics(overloads = "+")]' for example. This works inside of classes and in interfaces (although a cast is required... see testcase). To implement further operators slight changes to the check routines are required so that comparing operators may return bool.
Created attachment 271688 [details] Testcase
Created attachment 271695 [details] [review] Patch enabling operator overloading Updated the patch: Supported operators are now +, -, *, /, ==, !=, <, >, <=, >=.
Created attachment 271696 [details] Testcase
The way I see it operator overloading makes only sense for some cases: - operations where only arithmetic types are involved (+, -, *, /). This should only apply to value types. - comparisons (<, >, !=, ..) - string/list concatenation using +. My solution for the first and second case is to use python-like protocols. The first case would look something like this: [ArithmeticType (other="real")] struct Complex { Complex add (Complex c) Complex sub (Complex c) Complex mul (Complex c) Complex div (Complex c) Complex add_real (float real) Complex sub_real (float real) Complex rsub_real (float real) // used when Complex is the right operand Complex neg () // unary minus } Then the expression "c + 1" would first try to match against 'add' and after that fails against 'add_real'. In addition we can further restrict the protocols by allowing only arithmetic parameter types (IntegerType, FloatingType, ArithmeticType). This solution would have some advantages over general operator overloading: - It makes it clear what the types intended use is - it's more similar to vala's other constructs (get, set, contains) - it prevents 'creative' use of operators (think c++ streams) Of course the are also some disadvantages: - If a struct is bound as compact class, it can't be used as arithmetic type. - it enforces method names and thus complicates bindings. I think this is not a real problem. If overloading is desired [CCode (cname=...)] can be used to adjust the names. After that one would prefer the operators anyway.
Created attachment 272122 [details] [review] Patch enabling operator overloading Added test and fixed some inheritance issues.
Created attachment 272189 [details] [review] Patch enabling operator overloading In this updated version of the patch operator-overloading for Classes, Structs and Interfaces is supported. Two tests are included (one for structs and one for interfaces and classes).
Thanks for your work. If I were to merge this request, I'd first point out a couple of things: 1) [Semantics (overloads)] seems a little too complex. I'd prefer a simpler [Operator (type = "+")] or such. 2) For all the comparison operators, I'd rather add a [Compare] on top of a cmp method which returns an int, much like strcmp. I bet objects that are comparable have a compare method. Then vala can simply call compare() < 0, > 0, == 0, etc.
Sorry for late jumping in - would it be possible to make it work with Gee.Comparable somehow (http://www.valadoc.org/#!api=gee-0.8/Gee.Comparable)? Hashable (http://www.valadoc.org/#!api=gee-0.8/Gee.Hashable) also has a equal_to operator. It'd be better if we had a canonical way of comparation instead of separate for library(ies) and language. > 2) For all the comparison operators, I'd rather add a [Compare] on top of a cmp > method which returns an int, much like strcmp. I bet objects that are > comparable have a compare method. Then vala can simply call compare() < 0, > 0, > == 0, etc. There are types where you can reasonably implement == method but there is no prescribed comparation. To name one - by POSIX standart pthread_t can be compared by pthread_equal but there is no possibility of 'sorting' them. While ==, < and > usually work as pthread_t is usually typedef of pointer there are platforms in the wild where it is not the case. However I agree that there should be no need to have both != and ==, < and >= or > and <=.
(In reply to comment #15) > Sorry for late jumping in - would it be possible to make it work with > Gee.Comparable somehow (http://www.valadoc.org/#!api=gee-0.8/Gee.Comparable)? > Hashable (http://www.valadoc.org/#!api=gee-0.8/Gee.Hashable) also has a > equal_to operator. It'd be better if we had a canonical way of comparation > instead of separate for library(ies) and language. This can be achieved by simply adding the corresponding attributes to the Comparable-Interface (in the case that this patch is merged obviously). (In reply to comment #14) > Thanks for your work. If I were to merge this request, I'd first point out a > couple of things: > 1) [Semantics (overloads)] seems a little too complex. I'd prefer a simpler > [Operator (type = "+")] or such. > 2) For all the comparison operators, I'd rather add a [Compare] on top of a cmp > method which returns an int, much like strcmp. I bet objects that are > comparable have a compare method. Then vala can simply call compare() < 0, > 0, > == 0, etc. Both good ideas. On it. (In reply to comment #15) > There are types where you can reasonably implement == method but there is no > prescribed comparation. To name one - by POSIX standart pthread_t can be > compared by pthread_equal but there is no possibility of 'sorting' them. While > ==, < and > usually work as pthread_t is usually typedef of pointer there are > platforms in the wild where it is not the case. Maybe it makes sense to add both "[Compare]" which overloads all comparison-operators and "[Equals]" which overloads == and !=. Apart from that. After merging this it might be a good idea to implement "===" and "!==" operators which check reference (in)equality.
Created attachment 272853 [details] [review] Patch The patch is now more concise i think. Arithmetic overloading can now be done with [Operator (type = "+")] for example. I also added [Equals] and [Compare] attributes. A method marked with [Compare] should behave like strcmp and overloads all comparison operators. A method marked with [Equals] should return a bool and overloads only "==" and "!=".
A few notes: a) left.value_type.equals (right.value_type): this has an unobvious side-effect that if A has + overloaded and B derives from A and a and b have type B then a + b works, (A)a + (A)b works but (A)a + b and a + (A)b doesn't. It should be sufficient to check if both are compatible with arguments, b) Possibly it would even make sense to have multiple operators: Mat3x1 a = ... Mat1x3 b = ... a = a * 3.0; Mat3x3 c = a * b; double d = b * a; This can be left for future (or just scrapped). c) Are you sure that it'll be allowed to use Gee.Comparable? As far as I can tell Comparable<G> and G are not the same type.
(In reply to comment #18) > A few notes: > a) left.value_type.equals (right.value_type): this has an unobvious side-effect > that if A has + overloaded and B derives from A and a and b have type B then a > + b works, (A)a + (A)b works but (A)a + b and a + (A)b doesn't. It should be > sufficient to check if both are compatible with arguments, I intentionally left this out, because something that works not at all is better than something that works incorrectly. The problem i am seeing is that when A overloads + and B overloads + in a different way (which is allowed) then a + b would have a different result than b + a. This might be solvable, but it adds a truckload of extra complexity. > b) Possibly it would even make sense to have multiple operators: > > Mat3x1 a = ... > Mat1x3 b = ... > a = a * 3.0; > Mat3x3 c = a * b; > double d = b * a; > > This can be left for future (or just scrapped). This would certainly be nice. > c) Are you sure that it'll be allowed to use Gee.Comparable? As far as I can > tell Comparable<G> and G are not the same type. You are right. It would not work. But when i see it correctly Gee.Comparable makes it possible to compare two objects of different types. This is not desirable for the equality-operator anyway.
(In reply to comment #19) > (In reply to comment #18) > > A few notes: > > a) left.value_type.equals (right.value_type): this has an unobvious side-effect > > that if A has + overloaded and B derives from A and a and b have type B then a > > + b works, (A)a + (A)b works but (A)a + b and a + (A)b doesn't. It should be > > sufficient to check if both are compatible with arguments, > (...) > > c) Are you sure that it'll be allowed to use Gee.Comparable? As far as I can > > tell Comparable<G> and G are not the same type. > > You are right. It would not work. But when i see it correctly Gee.Comparable > makes it possible to compare two objects of different types. This is not > desirable for the equality-operator anyway. The problem is that it is the only way you can do it for generics in Vala in a 'sane' way. In that way you can write: class Test : Object, Comparable<Test> { public int compare_to (Test test) { // ... } } If argument was Comparable<Test> it would be: class Test : Object, Comparable<Test> { public int compare_to (Comparable<Test> _test) { Test? test = _test as Test; if (test != null) { // ... } else { // ??? } } } The proposed method couldn't be used by libgee as code is generic. So it comes back to original problem - you have two methods of writing equality - for language and library - which cannot be bridge even if libgee was to break API/ABI (which we were explicitly saying that we are not planning to do).
Created attachment 273235 [details] [review] Patch I disabled the check and made this feature compatible to Gee.Comparable. The only thing left to do to make this work is adding [Compare] to Gee.Comparable.compare_to(). This copies some of the drawbacks of the interface approach. It is now possible to do this: public class Foo { [Compare] public int compare (Bar bar) { return 0; } } public class Bar { } However... trying to use it will result in a compiler error, as TypeSymbol.get_binary_over() still checks if the two types are equal.
Created attachment 273236 [details] Testcase for Gee.Comparable Attached is a testcase that shows that when [Compare] is added to libgee the patch will be compatible to Gee.Comparable
-- GitLab Migration Automatic Message -- This bug has been migrated to GNOME's GitLab instance and has been closed from further activity. You can subscribe and participate further through the new bug through this link to our GitLab instance: https://gitlab.gnome.org/GNOME/vala/issues/4.