www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Virtual opBinary in interface

reply sfp <sfp hush.ai> writes:
Subject lines says it all, I think... The choice to make binary 
operators implementable only via this `opBinary` template means 
it's unclear how to get virtual operators on an interface. E.g., 
this toy example *does* compile:
```
interface Scalar {
   Scalar opBinary(string op)(Scalar rhs); // wrong
}

class Int : Scalar {
   int i;
   this(int i) { this.i = i; }
   Int opBinary(string op)(Int rhs) if (op == "+") {
     return new Int(i + this.i);
   }
}

void main() {
   Scalar one = new Int(1);
   Scalar two = one + one;
}
```
but with linker errors, of course:
```
~[...] $ dmd scratch.d
/usr/bin/ld: scratch.o: in function `_Dmain':
scratch.d:(.text._Dmain[_Dmain]+0x2a): undefined reference to 
`_D7scratch6Scalar__T8opBinaryVAyaa1_2bZQtMFCQBqQBlZQi'
collect2: error: ld returned 1 exit status
Error: undefined reference to `scratch.Scalar 
scratch.Scalar.opBinary!("+").opBinary(scratch.Scalar)`
        referenced from `_Dmain`
        perhaps `.d` files need to be added on the command line, 
or use `-i` to compile imports
Error: linker exited with status 1
        cc scratch.o -o scratch -m64 -Xlinker --export-dynamic 
-L/usr/lib64 -Xlinker -Bstatic -lphobos2 -Xlinker -Bdynamic 
-lpthread -lm -lrt -ldl
```
Could someone set me straight here? I just want to be able to 
define an interface that has some binary ops and then overload 
them as needed.

I am very new to D, and my goal is to learn how to solve this 
problem using classic, runtime, dynamic polymorphism in D (so, 
please don't suggest that I solve a different problem, suggest 
that I use a particular library, a different technique, etc.). 
Thanks in advance.
Dec 19
next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:
 
        perhaps `.d` files need to be added on the command line, 
 or use `-i` to compile imports
always try -i in response to any linker error
Dec 19
prev sibling next sibling parent reply user1234 <user1234 12.de> writes:
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:
 Subject lines says it all, I think... The choice to make binary 
 operators implementable only via this `opBinary` template means 
 it's unclear how to get virtual operators on an interface. 
 E.g., this toy example *does* compile:
 ```
 interface Scalar {
   Scalar opBinary(string op)(Scalar rhs); // wrong
 }

 [...]
Function templates declared in interfaces are not virtual, see https://dlang.org/spec/interface.html#method-bodies (§17.1.1.2), so as `Scalar.opBinary` has no body the linker cannot find the matching function.
Dec 19
parent sfp <sfp hush.ai> writes:
On Friday, 20 December 2024 at 01:29:32 UTC, user1234 wrote:
 On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:
 Subject lines says it all, I think... The choice to make 
 binary operators implementable only via this `opBinary` 
 template means it's unclear how to get virtual operators on an 
 interface. E.g., this toy example *does* compile:
 ```
 interface Scalar {
   Scalar opBinary(string op)(Scalar rhs); // wrong
 }

 [...]
Function templates declared in interfaces are not virtual, see https://dlang.org/spec/interface.html#method-bodies (§17.1.1.2), so as `Scalar.opBinary` has no body the linker cannot find the matching function.
Thanks. Yes, I surmised as much. I'm wondering if there is an idiomatic way to accomplish "virtual binary operators in an interface". The `opBinary` template gets in the way of this in a way that e.g. C++'s `operator+` and friends do not.
Dec 19
prev sibling next sibling parent mzfhhhh <mzfhhhh foxmail.com> writes:
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:
 Subject lines says it all, I think... The choice to make binary 
 operators implementable only via this `opBinary` template means 
 it's unclear how to get virtual operators on an interface. 
 E.g., this toy example *does* compile:
 ```
 interface Scalar {
   Scalar opBinary(string op)(Scalar rhs); // wrong
 }

 class Int : Scalar {
   int i;
   this(int i) { this.i = i; }
   Int opBinary(string op)(Int rhs) if (op == "+") {
     return new Int(i + this.i);
   }
 }

 void main() {
   Scalar one = new Int(1);
   Scalar two = one + one;
 }
 ```
The template methods in the interface need to be implemented within the interface. https://dlang.org/spec/interface.html#method-bodies ```d interface Scalar { Scalar opBinary(string op)(Scalar rhs) if (op == "+") { return add(rhs); } Scalar add(Scalar rhs); } class Int : Scalar { int i; this(int i) { this.i = i; } Scalar add(Scalar rhs) { return new Int((cast(Int)rhs).i + this.i); } } void main() { Scalar one = new Int(1); Scalar two = one + one; } ```
Dec 19
prev sibling next sibling parent reply =?UTF-8?Q?Ali_=C3=87ehreli?= <acehreli yahoo.com> writes:
On 12/19/24 10:49 AM, sfp wrote:
 Subject lines says it all
Although you clearly have a need for, virtual operators haven't been common in my experience. I always felt they could cause semantic issues. For example, the two subclasses of an interface may not have the binary relation that the interface prescribes. I can think of the Animal hierarchy where Cat and Dog may have a certain relationship that may not make sense between Alligator and Mouse. Forcing such a binary function at the Interface level may not be right. Having said that, and to contradict myself, this discussion reminded me of a fun DConf presentation by our friend Jean-Louis Leroy that included examples of such virtual functionality where it made sense: "Open Methods for D (The Expression Problem - solved)" https://www.youtube.com/watch?v=MpwHeE2Vvfw&t=395s But it uses his magical implementation (enabled by D) of open methods (and multi-methods). Ali
Dec 20
next sibling parent =?UTF-8?Q?Ali_=C3=87ehreli?= <acehreli yahoo.com> writes:
On 12/20/24 10:40 AM, Ali Çehreli wrote:
 I always felt they could cause semantic issues.
I remembered one such case. What should happen if both Cat and Dog defined the "+" operator? Should we expect 'cat + dog' behave the same as 'dog + cat'? Unfortunately, virtual functions are picked by the object that they are called on. The following example demonstrates this confusion with a function named mingleWith(). Different functions are called depending on the object. import std.stdio; interface Animal { void mingleWith(Animal); } class Dog : Animal { void mingleWith(Animal) { writeln("Dog with an Animal"); } } class Cat : Animal { void mingleWith(Animal) { writeln("Cat with an Animal"); } } void use(Animal a, Animal b) { a.mingleWith(b); b.mingleWith(a); // <-- DIFFERENT BEHAVIOR } void main() { auto c = new Cat(); auto d = new Dog(); use(c, d); } This is too much complication for engineering, program correctness, and life. :) Ali
Dec 20
prev sibling parent sfp <sfp hush.ai> writes:
On Friday, 20 December 2024 at 18:40:17 UTC, Ali Çehreli wrote:
 On 12/19/24 10:49 AM, sfp wrote:
 Subject lines says it all
Although you clearly have a need for, virtual operators haven't been common in my experience. I always felt they could cause semantic issues. ... But it uses his magical implementation (enabled by D) of open methods (and multi-methods). Ali
Right, the semantics can be a little odd, but there are definitely use cases for it which are very natural. Modeling how different kinds of animals mingle, I'm not sure... :-) "Virtual binary operators" are quite useful in computational science (my field...). For instance, with a numerical linear algebra library, you might have sparse matrices, dense matrices, diagonal matrices, Toeplitz matrices, matrices which are only accessible indirectly via their action (e.g. you multiply with a discrete Fourier transform matrix by applying the FFT and do not store the matrix itself), *block* matrices (comprised of other matrices), etc, etc. It is pretty common to mix and match these heterogeneously. MATLAB and Python have dense and sparse matrices, potentially other user-defined matrices/operators, and mixing and matching them is straightforward. Duck-typing is especially nice here, because there is no need to return a super type... multiply a dense matrix with a diagonal matrix and return a dense matrix (rather than a matrix supertype), no problem... I actually saw this multimethod implementation in another forum post when searching around, but not the talk. Thanks for posting it. I will definitely take a look, but unless I'm mistaken, D doesn't have free binary operators?
Dec 21
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:
 Subject lines says it all, I think... The choice to make binary 
 operators implementable only via this `opBinary` template means 
 it's unclear how to get virtual operators on an interface.
 I am very new to D, and my goal is to learn how to solve this 
 problem using classic, runtime, dynamic polymorphism in D (so, 
 please don't suggest that I solve a different problem, suggest 
 that I use a particular library, a different technique, etc.). 
 Thanks in advance.
As said virtual functions cannot be templates. However you can use alias to essentially define what you want your operators to be called. I wrote a blog post on how to use a single mixin to forward all operators to the D1 style overloads. You might find it useful or inspiring. https://www.schveiguy.com/blog/2022/06/how-to-keep-using-d1-operator-overloads/ -Steve
Dec 20
next sibling parent Andy Valencia <dont spam.me> writes:
On Saturday, 21 December 2024 at 07:02:07 UTC, Steven 
Schveighoffer wrote:
 I wrote a blog post on how to use a single mixin to forward all 
 operators to the D1 style overloads. You might find it useful 
 or inspiring.

 https://www.schveiguy.com/blog/2022/06/how-to-keep-using-d1-operator-overloads/
Thank you for that remarkably informative article! I picked up quite a number of new techniques from it. Andy
Dec 21
prev sibling parent sfp <sfp hush.ai> writes:
On Saturday, 21 December 2024 at 07:02:07 UTC, Steven 
Schveighoffer wrote:
 I wrote a blog post on how to use a single mixin to forward all 
 operators to the D1 style overloads. You might find it useful 
 or inspiring.

 https://www.schveiguy.com/blog/2022/06/how-to-keep-using-d1-operator-overloads/

 -Steve
Thank you so much for posting this! This is exactly what I was hoping for. I saw that D used to have this other style of binops, but their state was unclear to me. This is a really cool collection of tricks and will definitely help me solve my current problem and keep learning cool D tricks. Cheers!
Dec 21