Add `minus` and `plus` to `BigNumber` abstract class.
We have a class like this--
class Math
{
public static function minimumOf(BigNumber $a, BigNumber $b): BigNumber
{
return $a->isLessThanOrEqualTo($b) ? $a : $b;
}
public static function maximumOf(BigNumber $a, BigNumber $b): BigNumber
{
return $a->isGreaterThanOrEqualTo($b) ? $a : $b;
}
}
Now, if we pass instances of BigDecimal to minimumOf and want to plus a number to BigNumber that is returned (an instance of BigDecimal, that's not possible because plus and minus don't exist in the abstract class. What I need to do now is call another method in the chain toBigDecimal before being able to do so. It's not clean. I see no problem adding them to the abstract class. What do you say?
Hi,
I'll split my comment into multiple parts as this will be a pretty long one.
Now, if we pass instances of
BigDecimaltominimumOfand want toplusa number toBigNumberthat is returned (an instance ofBigDecimal, that's not possible becauseplusandminusdon't exist in the abstract class.
Actually, what you're getting is a BigDecimal, which does have plus(); PHP will allow it:
Math::minimumOf($bigDecimal1, $bigDecimal2)->plus($bigDecimal3); // works
The issue is that your IDE does not understand it, because it only knows about BigNumber at this point.
To fix this, you can use generics syntax, which is understood by static analysis tools (Psalm, PHPStan), as well as PhpStorm (not sure about other IDEs):
/**
* @template T of BigNumber
*
* @param T $a
* @param T $b
*
* @return T
*/
public static function minimumOf(BigNumber $a, BigNumber $b): BigNumber
{
return $a->isLessThanOrEqualTo($b) ? $a : $b;
}
I'll also note that BigNumber already provides min() and max() static methods, which are typed as returning static, so this will be already understood by your IDE:
BigDecimal::min($bigDecimal1, $bigDecimal2)->plus($bigDecimal3);
Note that we're using BigDecimal::min() and not BigNumber::min() here.
With that being said, I checked and it would be possible to add these methods to BigNumber:
abstract public function plus(BigNumber|int|float|string $that) : static;
abstract public function minus(BigNumber|int|float|string $that) : static;
abstract public function multipliedBy(BigNumber|int|float|string $that) : static;
abstract public function dividedBy(BigNumber|int|float|string $that) : static;
Even dividedBy(), which takes optional parameters after $that in BigInteger and BigDecimal, allows this because it still respects the LSP.
I'm hesitant to add those to BigNumber though, as I feel like it might be confusing for two reasons:
- These methods all accept parameters that must be convertible to an instance of the current class, and return an instance of the current class; so it might be surprising that this would not work:
Which is less of a surprise if you explicitly start with aBigNumber::of(1)->plus(2.5); // RoudingNecessaryException, because BigNumber::of(1) is a BigIntegerBigInteger:BigInteger::of(1)->plus(2.5); // RoudingNecessaryException -
BigNumber::dividedBy()would not present the optional parameters that come after$thatin subclasses, so for example this would fail, with no evident way to fix it:
Whereas when called onBigNumber::of(15)->dividedBy(2); // RoundingNecessaryExceptionBigInteger, it is obvious from the method signature that you can do:BigInteger::of(15)->dividedBy(2, RoundingMode::DOWN); // 7
That makes me think that a new API that could be useful on BigNumber is a way to perform "type juggling" between BigNumber classes:
class BigNumber
{
abstract public function plusAuto(BigNumber|int|float|string $that) : BigNumber;
abstract public function minusAuto(BigNumber|int|float|string $that) : BigNumber;
abstract public function multipliedByAuto(BigNumber|int|float|string $that) : BigNumber;
abstract public function dividedByAuto(BigNumber|int|float|string $that) : BigNumber;
}
These would behave differently from their counterparts without Auto:
- They would not require the operand to be convertible to an instance of the class on which the method is called;
- They would not give any guarantee about the return type other than being a
BigNumberinstance.
So this would become possible:
$a = BigNumber::of(1); // BigInteger(1)
$b = $a->dividedByAuto(2); // BigDecimal(0.5)
$c = $a->dividedByAuto(3); // BigRational(1/3)
What do you think about all this?