February 6, 2020 23:36
```On Wed, 2020-01-29 at 00:14 +0100, jan.h.boehmer@gmx.de wrote:
> the last days I have experimented a bit with operator overloading in
> userspace classes (redefing the meaning of arithmetic operations like

Some historic context: I am probably the one who did operator
https://markmail.org/message/y7rq5vcd5ucsbcyb

>
> Here you can find some basic demo code using it:
> <https://gist.github.com/jbtronics/ee6431e52c161ddd006f8bb7e4f5bcd6>
> https://gist.github.com/jbtronics/ee6431e52c161ddd006f8bb7e4f5bcd6
>

This example can be used to show a major problem for PHP doing this.

The first problem is that PHP historically had very few type
annotations making the code hard to predict, modern PHP has more of
that reducing this a bit, but the big problem remains: In PHP we can't
overload functions, thus operators have to be member functions and
therefore form a closed set.

In your example the vector3 can operate on vector3s.

\$a = new Vector3(1, 2, 3);
\$b = new Vector3(3, 2, 1);

\$c = \$a * \$b;

Within Vector3 that is complete. But maths allows multiplication with
integers. So, yes, you can extend your __mul() with a check for the rhs
as you did with the is_numeric, but why would

\$c = 2 * \$a;

call the Vecotr3's operator function? I believe it would call integer's
operator. Which obviously doesn't exist.

But okay, let's do a hack for integer, to call the second arguments
operator if first argument is an integer.

Now I come and really like your vector3 library and create my Matrix
type. With my Matrix i want to still use your Vector3.

include 'your/vector3.php';
class Matrix {
public static function __mul(\$lhs, \$rhs) { ... }
}

\$vec = new Vector3(...);
\$matrix = new Matrix(...);

\$result = \$vec * \$matrix;

Which one is being called? - Vector's or Matrix's.  How will your

The way C++ solves this is by allowing non-member functions as
operators.

#include "vector3.h" // provides class Vector3
#include "matrix.h"  // provides class Matrix, potentially
//  from a different vendor

Matrix operator*(const Vector3 &lhs, const Matrix &rhs) {
// I can provide  this myself if neither Vctor's nor
// Matrix's vendor do
return ...;
}

int main() {
Vector3 vec{...};
Matrix matrix{....};

// works
auto result = vec * matrix;
}

To make this really work C++ has another magic, aside from function
black magic of C++: If The function to be called is not only looked for
in the current or global namespace, but also the namespace of the first
Argument. So this works:

namespace JohannesCoolLibrary {
class Vector;

void func(Vector v);
Vector operator*(Vector lhs, Vector rhs);
}

namespace SomeOtherCoolThing {
JohannesCoolLibrary::Vector v1{};
JohannesCoolLibrary::Vector v2{};

v1 * v2; // will find the operator in the namespace
// even though it's using the global name

func(v1); // will also call function from argument's namnespace
}

Without these features you can only create closed types, which
massively limit interoperability, which massively limits the use cases

With limited set of use cases, this is a rare feature, which is hard to
understand, or how many internals reads do you expect immediately know
the output of

function a(\$a, \$b) {
return \$a + \$b;
}

var_dump(a(, ));

johannes```
February 7, 2020 20:48
```Hi Johannes,

Thank you for your points! I think you point out some overlooked issues.

Johannes SchlÃ¼ter wrote:
> Which one is being called? - Vector's or Matrix's.  How will your
> vector know about my Matrix?
>
> The way C++ solves this is by allowing non-member functions as
> operators.
>
>       #include "vector3.h" // provides class Vector3
>       #include "matrix.h"  // provides class Matrix, potentially
>                            //  from a different vendor
>
>       Matrix operator*(const Vector3 &lhs, const Matrix &rhs) {
>           // I can provide  this myself if neither Vctor's nor
>           // Matrix's vendor do
>           return ...;
>       }
>
>       int main() {
>           Vector3 vec{...};
>           Matrix matrix{....};
>
>            // works
>            auto result = vec * matrix;
>       }

I wonder if it would be a good idea, if we do want operator overloading
in PHP, to implement a similar mechanism for this. Perhaps type-specific
overloads could be registered via some special function call or
declaration, something vaguely like:

class Vector {
public function __construct() {
php\register_overload(\$this, Matrix::class, '*', function (\$a, \$b) {
/* multiplication implementation here */
});
}
}

The engine could then do type-matching for you, and would implement
commutativity for you if the operator is commutative, so `\$someVector *
\$someMatrix` would call the above function, but so would `\$someMatrix *
\$someVector`. (Note: to support matrix multiplication, I guess
commutativity must be overridable. Also, I have forgotten whether
multiplying a matrix and a vector is commutative or not :p)

I think this approach would be less messy than having to implement full
type matching on both sides of a type pair, for a number of reasons:

* Instead of Vector having to have an implementation of __mul which
checks for Matrix, and Matrix having to have an implementation of __mul
which checks for Vector, just one of these types can call
register_overload with a single implementation (because the operation is
commutative).
* Whether two types can be used with a particular operator is clear:
either there is such a pair registered, or there is not, and PHP can
give appropriate error messages. It is unlikely there will be an issue
where one side has __mul but it just throws an exception or somethig.
Also, in a case like `\$a * \$b`, \$a can implement support for \$b without
\$b having to support \$a, while at the same time \$b can implement support
for some unrelated other type, without `\$b * \$a` not working (with the
current proposal, imagine \$a's __mul handler supporting \$b but not
vice-versa).
* The engine can see conflicts (one class declares an overload involving
the other class, and vice-versa) and warn about them, rather than `\$a *
\$b` silently having completely different behaviour to `\$b * \$a`.

This is not to say we necessarily should implement this, but it may be