Jump to page: 1 2
Thread overview
Virtual opBinary in interface
Dec 19
sfp
Dec 19
monkyyy
Dec 20
user1234
Dec 20
sfp
Dec 20
mzfhhhh
Dec 21
sfp
Dec 21
sfp
December 19

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.

December 19

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

December 20

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.

December 20

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

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;
}
December 20

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.

December 20
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

December 20
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

December 21

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

December 21

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

December 21
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?
« First   ‹ Prev
1 2