> 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

Reply via email to