On Fri, Apr 5, 2024 at 2:48 AM Tim Düsterhus <t...@bastelstu.be> wrote:

> Hi
>
> Your `Money` example would allow for unsound and/or non-sense behavior,
> such as:
>
>      $fiveEuros = new Money(5, 'EUR');
>      $tenDollars = new Money(10, 'EUR');
>
>      $what = $fiveEuros + $tenDollars;
>
> What would you expect to be in $what? A `BcMath\Number(15)`?
>
> ----------------
>
> The BcMath\Number class *should* absolutely be final, as a number is a
> number is a number. Allowing extension just to be able to write
> $number->isPrime() instead of isPrime($number) will allow for very
> confusing code, such as the example above, with no way to work around it
> in userland. It also makes interoperability between two different
> libraries that expect their own extensions to work very painful.
>
> Consider the following example:
>
>      class PrimalityTestingNumber extends Number {
>          public function isPrime(): bool { }
>      }
>
>      class ParityTestingNumber extends Number {
>          public function isEven(): bool { }
>          public function isOdd(): bool { }
>      }
>
> If I now want to create a function to check whether a number is an even
> prime, I need to do something like this:
>
>      function isEvenPrime(Number $n) {
>          return (new PrimalityTestingNumber($n))->isPrime() && (new
> ParityTestingNumber($n))->isEven();
>      }
>
> This use case would be much better solved in a generic way using
> something like this "Extension Methods" proposal:
> https://externals.io/message/118395#118395
>
>
Well, since they are both in Euros, I would expect 15, but I expect that
was a typo considering the variable name and it was meant to be 10 USD.

As I said, you can accomplish the same through composition. For a money
class, you'd likely need an extended method that checks the currency first,
and then makes a call to the actual calculation method, which is
essentially what the composing class would do as well, something like
`addCurrency()`.

Frankly, I don't think that making it final actually makes the API more
resistant to errors, but it's also not something I care about enough to
really fight for it on this RFC. I think it's the wrong choice, because the
one example that I pulled up in this discussion does not constitute the
entire breadth of use cases for this type of object, and I find myself
extremely hesitant to suggest that we have thought of and considered all
the various use cases that developers might have, or how they might be
impacted by making the entire class final.

Making it final will not reduce errors in my opinion, it will just make
internals feel like those errors are less "our fault". A composed class
does not somehow prevent the accidental error of mixing currencies, it just
moves where that error would occur. Forcing composition drastically reduces
the usability of the operator overloads, which I am opposed to, and I do
not believe that this is being given the same kind of consideration. Once
the class is forced to be composed, it can no longer be used as part of any
kind of extensions either.

I mentioned that I have a library that adds arbitrary precision functions
for trigonometric functions (among others), to extend BCMath (and
ext-decimal). A library that is solely focused on additional arbitrary
precision math features should be the first in line to use this RFC, but I
can tell you that if this class is final, I won't even update my library to
use it at all, because there will be no point. The reason I did not mention
MY use case is because I don't think many PHP developers are out there
maintaining their own complicated trigonometric extensions to BCMath, so I
don't think it's a good example of a common use case.

Making it final also breaks with how other internally provided classes have
been done in the past, many with no discernable issues. I do not see any
mailing list discussions about the problems with DateTime being open for
extension.

If you want an actual answer about how a Money class would actually work in
practice, it would likely be something like this:

```
// $val1 and $val2 are instances of the Money class with unknown currencies
$val1Currency = $val1->getCurrency();
$val2Currency = $val2->getCurrency();
$val1 = $val1->convertToCommonCurrency();
$val2 = $val2->convertToCommonCurrency();
// perform the necessary calculations using operators
$answer = $answer->convertToCurrency($userCurrency);
```

I would expect that most applications dealing with money are converting to
a common calculation currency prior to actually doing any calculations,
instead of relying on the conversion magically happening inside their
calculation methods.

So, your argument leaves me entirely unmoved. However, as I said, while
this makes the RFC essentially useless to me, I don't think it's worth
rejecting the RFC over, so I'll leave it at that.

Jordan

Reply via email to