Jump to page: 1 25  
Page
Thread overview
Argumnentation against external function operator overloading is unconvincing
Sep 21, 2016
HaraldZealot
Sep 21, 2016
Ilya Yaroshenko
Sep 21, 2016
Timon Gehr
Sep 21, 2016
Timon Gehr
Sep 21, 2016
jmh530
Sep 21, 2016
HaraldZealot
Sep 21, 2016
H. S. Teoh
Sep 22, 2016
HaraldZealot
Sep 22, 2016
HaraldZealot
Sep 22, 2016
H. S. Teoh
Sep 22, 2016
HaraldZealot
Sep 22, 2016
HaraldZealot
Sep 22, 2016
HaraldZealot
Sep 22, 2016
Jonathan M Davis
Sep 23, 2016
Timon Gehr
Sep 23, 2016
Stefan Koch
Sep 23, 2016
Timon Gehr
Sep 24, 2016
krzaq
Sep 21, 2016
Timon Gehr
Sep 22, 2016
pineapple
Sep 22, 2016
Jonathan M Davis
Sep 24, 2016
Martin Nowak
Sep 22, 2016
H. S. Teoh
Sep 25, 2016
pineapple
Sep 25, 2016
Jonathan M Davis
Sep 25, 2016
pineapple
Sep 25, 2016
Jonathan M Davis
Sep 25, 2016
ZombineDev
Sep 25, 2016
pineapple
Sep 25, 2016
pineapple
Sep 25, 2016
Jonathan M Davis
Sep 25, 2016
pineapple
Sep 25, 2016
Jonathan M Davis
Sep 25, 2016
pineapple
Sep 25, 2016
Jonathan M Davis
Sep 25, 2016
pineapple
Sep 25, 2016
Jonathan M Davis
Sep 25, 2016
Walter Bright
Sep 23, 2016
Sai
Sep 23, 2016
Jonathan M Davis
September 21, 2016
In current D, overloading operator like "+" with external function is prohibited. There is the rationale [1] (see second paragraph).

BUT this rationale is totally UNCONVINCING. I can say that resume of rationale is: "it is impossible because it brakes some C++ patterns and behaviour". Further I will try to demonstrate such resume.

Let start with an example where external operator overloading can be useful. Imagine,  we want to create some lazy matrix algebra, where all operation return new object of corresponding to operation subtype. E.g. `transpose` returns `TransposedMatrix`, where `opIndex` just call `opIndex` of internally stored matrix with switched order of indices, `plus` returns `MatrixSum`, where `opIndex` just calculated the sum of two corresponding elements of stored matrices and so on. Of course we want that any resulting subtypes can interact with each other, so we require common "denominator". There are two ways compile-time duck-typing  with template bloating, or common interface (yeah, with garbage collection in the simplest implementation), but the last allows us to have some run-time parameters. So regard common interface approach: our interface should be minimal as possible, for our purposes having `rows`, `columns` and `opIndex` seems to be sufficient.
OK, we have:
```d
Matrix plus(Matrix A, Matrix B)
{
    final class MatrixSum : Matrix
    {...}
    ...
    return new MatrixSum(A, B);
}
...
C=plus(A, B);
```
(Or even `C=A.plus(B)` because of UFCS). And this works perfect.
If we want some sugar like `C=A+B` we are in trouble. We can create function `Matrix opBinary(string op)(Matrix A, Matrix B) if(op == "+")`, it even can be called with full name, but not with "+" operator, because now compiler just doesn't look for external function as overload for operator. If we want have this as member we should ad opBinary to our interface, what leads to some new trouble: now each type should have opBinary, but how we should implement this for example for `TransposedMatrix` or even more interesting (because of kind of type recursion) how we should implement it for `MatrixSum`. OK, me and Mathias Lang have found workaround for my particular case we should implement (and probably make `final`) opBinary for `Matrix` interface, which in its turn calls function `plus` we already described. But its create lines of code for nothing, because operators are considered as too exceptional.

Let us return to rationale.
First point is "Operator overloading can only be done with an argument as an object". Why??? it seems to come from "C++ mind", before UFCS was implemented in language. If we have `1.to!string` and `A.plus(B)` working, what wrong with `A+B` or `2*B` (where A and B matrices). Moreover for case like `2*B` (multiplication by scalar) we should have a bit ugly `opBinaryRight(string op)(double scalar)` as member function. It isn't convinced.

Second point is "Operator overloads USUALLY need access to private members of a class". It has nothing common with operator, it is only demonstrate  our C++ behave to work with private members in operator. But having access to private member isn't necessary for operator to work. In my example `plus` function uses only public API of my classes, it can be done such way in many, many cases. And also often this external function would be placed in the same module, so it is already have "friend" in such case. If external function in external module want to have access to private API, it is only a sign of bad design, and should be a problem of particular programmer how to rework the design, not case for compiler to step in. It isn't convinced.

Third point is totally unnecessary because we don't need access to private members.

So, no one of point convinces me.

What can really can convince me that rationale like: "if we do so total parsing algorithm will be corrupted, and we can't use D any-more", or at least "with that feature compiler becomes 100 times slower".


So if someone has real rationale not to have operator overloading as external function I'm curios to arguments.


[1] http://dlang.org/rationale.html
September 21, 2016
On Wednesday, 21 September 2016 at 17:57:17 UTC, HaraldZealot wrote:
> In current D, overloading operator like "+" with external function is prohibited. There is the rationale [1] (see second paragraph).
>
> [...]

I am completely agree. We should support external operator overloading for ndslice extension. - Ilya
September 21, 2016
On 21.09.2016 19:57, HaraldZealot wrote:
>
>
> So if someone has real rationale not to have operator overloading as
> external function I'm curios to arguments.
>
>
> [1] http://dlang.org/rationale.html

There is no technical reason that would make the implementation of this feature difficult, if that is your question.

Basically, the rationale is: external operators cannot be used in generic code that does not import the module defining the operators. C++ works around this using ADL. Walter (justifiably) does not like ADL, hence the limitation.

(I don't agree with that line of reasoning: obviously this is not only an issue for operators, but for any UFCS function; operators are mere syntactic sugar.)
September 21, 2016
On 21.09.2016 21:01, Timon Gehr wrote:
> On 21.09.2016 19:57, HaraldZealot wrote:
>>
>>
>> So if someone has real rationale not to have operator overloading as
>> external function I'm curios to arguments.
>>
>>
>> [1] http://dlang.org/rationale.html
>
> There is no technical reason that would make the implementation of this
> feature difficult, if that is your question.
>
> Basically, the rationale is: external operators cannot be used in
> generic code that does not import the module defining the operators. C++
> works around this using ADL. Walter (justifiably) does not like ADL,
> hence the limitation.
>
> (I don't agree with that line of reasoning: obviously this is not only
> an issue for operators, but for any UFCS function; operators are mere
> syntactic sugar.)

BTW, another argument in favour of free function operators is opOpAssign for classes.
September 21, 2016
On Wednesday, 21 September 2016 at 19:01:40 UTC, Timon Gehr wrote:
>
> Basically, the rationale is: external operators cannot be used in generic code that does not import the module defining the operators.

So why not have the struct/class explicitly import external operators? You can do this currently with a separate module defining them and then the struct/class imports the whole thing. Kind of annoying, but would get the job done, no?

Alternately, you could invent some new syntax. Maybe something like

import this : Matrix plus(Matrix A, Matrix B);


September 21, 2016
On Wednesday, 21 September 2016 at 19:01:40 UTC, Timon Gehr wrote:
>
> Basically, the rationale is: external operators cannot be used in generic code that does not import the module defining the operators.

Could you give some elaborate example, for now I can't imagine what your mean.
September 21, 2016
On Wed, Sep 21, 2016 at 08:53:06PM +0000, HaraldZealot via Digitalmars-d wrote:
> On Wednesday, 21 September 2016 at 19:01:40 UTC, Timon Gehr wrote:
> > 
> > Basically, the rationale is: external operators cannot be used in generic code that does not import the module defining the operators.
> 
> Could you give some elaborate example, for now I can't imagine what your mean.

Here's a simple example:

	// usertype.d
	module usertype;
	struct UserType { ... }
	auto opBinary(string op : "+")(UserType u1, UserType u2) { ... }

	// generic_code.d
	module generic_code;
	auto algorithm(T,U)(T t, U u) {
		return t + u;
	}

	// main.d
	module main;
	import usertype;
	import generic_code;
	void main() {
		UserType u1, u2;
		auto r = u1 + u2;		// OK
		auto s = algorithm(u1, u2);	// NO GOOD
	}

The problem here is that generic_code.d doesn't (and shouldn't!) import
usertype.d, so usertype.opBinary is not visible in generic_code.d. So
when algorithm() tries to look up the '+' operator in `t + u`, it can't
find the declaration and fails.  There is no way to find the correct
opBinary() because it's not part of UserType, so algorithm() has no way
to access that symbol.

Using the operator in module main is OK, because main (rightfully) imports usertype.d, so the operator is visible. But any generic code that main imports will have a problem because they can't (and shouldn't!) know ahead of time which modules contain the declaration they need.

In C++ this problem is solved using ADL, but ADL brings with it other problems, the root of which is that it breaks module encapsulation.

There's actually some instances of this problem (w.r.t. UFCS) in Phobos, that currently requires ugly workarounds like importing modules that the generic code really shouldn't be depending on.


T

-- 
Computerese Irregular Verb Conjugation: I have preferences.  You have biases.  He/She has prejudices. -- Gene Wirchenko
September 21, 2016
On 21.09.2016 22:53, HaraldZealot wrote:
> On Wednesday, 21 September 2016 at 19:01:40 UTC, Timon Gehr wrote:
>>
>> Basically, the rationale is: external operators cannot be used in
>> generic code that does not import the module defining the operators.
>
> Could you give some elaborate example, for now I can't imagine what your
> mean.

module a;

struct Foo{}

Foo opBinary(string op:"+")(Foo a, Foo b){ return Foo(); }

---

module b;

T add(T)(T a,T b){
    return a + b;
}

---

module c;
import a,b;

void main(){
    Foo x=add(Foo(),Foo()); // error
}

September 22, 2016
On Wednesday, 21 September 2016 at 21:14:15 UTC, H. S. Teoh wrote:
>

Thank you both, I see now.

So it seems to be essential point. But because we already have the same problem with UFCS, I don't see why we should prohibit external overloading of operator, it is just inequality (in political sense) for operators :)

In any case we at least should change rationale, because current three points are have nothing with real problem and shouldn't be obstacle.

But probably the best solution is implementing external operator overloading, just to omit special case. And problem with generic code solve independently for all UFCS functions including operators.
September 22, 2016
On Thursday, 22 September 2016 at 05:38:53 UTC, HaraldZealot wrote:
> And problem  with generic code solve independently for all UFCS functions including operators.

Unfortunately I don't know compiler and all related stuff internally (time to change this ;) ), but it seems to me there is a way to solve problem with UFCS and generic code without breakage of module encapsulation. If understand correctly when we instantiate template with struct/class we pass to template some kind of "static signature of type" (if I may it call so). With the static signature of type I mean all member function of class or struct. If we instead of this before instantiate any type (including POD) create "dynamic signature of type" which include all visible at the moment of call function and templates for this type.
« First   ‹ Prev
1 2 3 4 5