Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:108421 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 28522 invoked from network); 7 Feb 2020 01:24:25 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 7 Feb 2020 01:24:25 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id EDCD71801FD for ; Thu, 6 Feb 2020 15:37:10 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.5 required=5.0 tests=BAYES_05,SPF_HELO_PASS, SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS31103 84.19.160.0/19 X-Spam-Virus: No X-Envelope-From: Received: from mail.toneristzuen.de (mail.toneristzuen.de [84.19.169.162]) by php-smtp4.php.net (Postfix) with ESMTP for ; Thu, 6 Feb 2020 15:37:10 -0800 (PST) Received: from maniacmansion.fritz.box (ppp-188-174-62-162.dynamic.mnet-online.de [188.174.62.162]) by mail.toneristzuen.de (Postfix) with ESMTPSA id 6003B46A55; Fri, 7 Feb 2020 00:37:08 +0100 (CET) Message-ID: <8cad7201fae0b5e421e6a064e93ff21614a42e31.camel@schlueters.de> To: jan.h.boehmer@gmx.de, internals@lists.php.net Date: Fri, 07 Feb 2020 00:36:53 +0100 In-Reply-To: <00ea01d5d630$b18d4f20$14a7ed60$@gmx.de> References: <00ea01d5d630$b18d4f20$14a7ed60$@gmx.de> Content-Type: text/plain; charset="UTF-8" X-Mailer: Evolution 3.28.5-0ubuntu0.18.04.1 Mime-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: Re: [PHP-DEV] Operator overloading for userspace objects From: johannes@schlueters.de (Johannes =?ISO-8859-1?Q?Schl=FCter?=) 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 overloading in PHP first. Oldest trace is this post: https://markmail.org/message/y7rq5vcd5ucsbcyb > > Here you can find some basic demo code using it: > > 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 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; } To make this really work C++ has another magic, aside from function overloading, which is ADL - Argument depending lookup, which is the 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 for operator overloading. 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([1], [2])); johannes