> On Aug 11, 2021, at 12:06 AM, Jordan LeDoux <jordan.led...@gmail.com> wrote: > > Here is a quick and dirty example of just the complex number use case. It > assumes that the operator overloads already are built in. A couple of > things of note:
Excellent. Thank you for taking the time to do this. Very helpful. One thing I looked for at first but did not find was an actual example of how you would use them vs. how they would be implemented. So I forked it and created such a file to see if I could understand how they would be used: https://github.com/mikeschinkel/operator-overloading-rfc/blob/master/use-cases/complex-numbers/example.php It that what you envision? To create a ComplexNumber() do I first need to wrap in Real() and in Imaginary()? Or where you intending I could just pass numeric literals? If yes, PhpStorm said your type hints did not allow it. > 1. The actual mathematical logic to cover all the possible input and return > types is extensive. Though the execution time and complexity are quite > limited, the code complexity is high due to the number of conditionals. Yes, the complexity overwhelmed me at first glance, but I don't think it needs to be so complex, which I will get to below. > 2. I chose to implement the various operator overload functions in > different ways to illustrate different methods that might accomplish the > same task in userland code. For instance, in some cases I typed against the > SimpleNumber abstract, while in others I expanded SimpleNumber out into its > concrete implementations, Real and Imaginary. Yeah, that kind of threw me to, because I looked at the code before I read your email. And it still throws me because I'm not 100% clear how SimpleNumber is supposed to behave. 1. In your examples would I use `SimpleNumber` anytime I see `Imaginary|Real`? 2. I notice in SimpleNumber methods you pass SimpleNumber $self as a first parameter to __add() and __mul() and in ComplexNumber you pass ComplexNumber $self as a first parameter, but in ComplexNumber you never use it. Obviously the __add() and __mul() signatures would need to be equivalent, but I didn't grok why you didn't just use $this instead of $self so I omitted that parameter in my fork. 3. I'm not sure why we even need a `Real` class, other than the fact PHP doesn't (currently?) support operations on numeric literals and imaginary numbers. Check my logic here but if PHP understood complex numbers then `Real` could just be a `float`. But with just userland operator overloading we have to wrap a float inside an instance of Real, right? 4. It looks like you added a $left = bool parameter but never used it. I assume the intent is that PHP for some reason might want to convert `$x + $y` to $y->__add($x,true)? When would it need that? Seems that the $left parameter just makes the signature more complex. I dropped it from my fork. Also, this really cries out for userland type definitions and type aliases (which can and IMO should be two distinctly different things, btw.): `SimpleNumber|ComplexNumber|int|float $other` > 3. I only implemented the _add() and __mul() methods for this example, as > the reality is that getting into some of the more complex operations would > be so much code that it wouldn't be a digestible example. The __pow() > method for ComplexNumber would be in excess of 100 lines and also require a > polar coordinate representation within the class, for instance. Sure. But it would be nice if you could also stub out __pow() and any others abstract methods so the full list of expected operations and their parameters. BTW, when I asked I wasn't expecting to see an implementation for any of the methods, I was just asking for an interface. You went above and beyond that. And, since you are writing code that won't run anyway, why not go ahead and assume a `typedef`? That would greatly reduce the visual complexity of your example: typedef Number: Imaginary|Real|SimpleNumber|ComplexNumber|int|float; > This isn't a working implementation, in that this code will produce errors > if run in PHP (due to the absence of operator overloads). Please note that > if we were to go the route of creating domain-specific classes to cover > this use case, the actual classes would be many, many times longer than > this. Yes, of course. > This is some of the simplest logic involved in this particular use > case, which is why I chose it as the example, and that perhaps illustrates > why I am willing to explore such objects but do not believe they are the > best choice at this time. So I promised to explain how to simplify the logic. This has nothing to do with operator overloads so anyone reading who is only interested in the RFC can stop reading now. -Mike ---- There is a concept called the "Happy Path"[1] which is " is a default scenario featuring no exceptional or error conditions." It is often used to refer to testing, but there is an emerging trend where people are arguing that you should align the happy path with the left edge of your code[2][3]. A similar mantra is: Avoid Nesting. Code that is more nested is now being recognized as more complex than less nested code[4]. With that in mind I refactored your SimpleNumber and ComplexNumber classes using the following strategies: 1. Use the simplest if {} expressions you can. That means no `||` or `&&` expressions 2. Wrap `||` and `&&` expressions into named methods, i.e. `isRealNumber()` instead of `is_int() || is_float() || instanceof Real` OR list them out as multiple if {} statements. 3. For `&&` you can also invert the logic into OR logic and create multiple if {} statements, e.g. `$realPart->abs() != 0 && $imaginaryPart->abs` in ComplexNumber::__add() becomes two if {} statements. 4. When it appears impossible to avoid nesting an additional level because of the requirements of the logic then that code it telling you to break it out into a named function to be called, e.g. addParts(), multiplyParts(), multiplyRealPart() and multiplyImaginaryPart() for ComplexNumber. 5. Remove all "else" statements to unravel the logic so that all conditional branching logic is nested at most one additional level. 6. And this final part is controversial because so many developers have bought in to dogma that obscures the approach. Fortunately there are people like Linus Torvalds who have been able to see past anti-GoTo dogma[5]. Basically the refactoring approach is to use a `goto end;` instead of an early return. That provides numerous benefits which I document here[6]. You can find the fork of your code that illustrates all those techniques to reduce code complexity here[7]. After reviewing it I hope you will agree that the algorithms become much more obvious when using this strategy for coding. But again, this coding strategy has nothing to do with operator overloading, it was just something I did to help me be better able to understand your use-case example. I'll submit a PR just on the off-hand case you want to pull in some or all the code. [1] https://en.wikipedia.org/wiki/Happy_path [2] https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88 [3] https://maelvls.dev/go-happy-line-of-sight/ [4] https://ieeexplore.ieee.org/document/6693981 [5] https://web.archive.org/web/20130305022050/http://kerneltrap.org/node/553/2131 [6] https://github.com/mikeschinkel/go-only#benfits [7] https://github.com/mikeschinkel/operator-overloading-rfc/tree/master/use-cases/complex-numbers -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php