rfc:operator_overrides_lite

This is an old revision of the document!


PHP RFC: Operator Overrides -- Lite Edition

Introduction

Nearly three years ago, PHP RFC: User Defined Operator Overloads was declined due to scope and new syntax concerns. However, the GMP class, which represents numbers in the GMP extension, was (accidentally) left non-final. This RFC explores the potential of extending GMP with a limited form of operator overriding, providing cleaner expressions for mathematical constructs and allowing new types of numerical representations such as units, complex numbers, and more.

Proposal

The \GMP class will have the following methods added as protected static:

  • add(mixed $left, mixed $right): static
  • subtract(mixed $left, mixed $right): static
  • multiply(mixed $left, mixed $right): static
  • divide(mixed $left, mixed $right): static
  • mod(mixed $left, mixed $right): static
  • exp(mixed $left, mixed $right): static
  • comparable(mixed $left, mixed right): bool

These methods will enable operator-like behavior when extended. Overriding equals, less-than, greater-than, etc., is not included to maintain simplicity and avoid non-sensical comparisons.

Binary operations will be handled as such:

  1. If both operands are instances of `\GMP`, standard behavior applies.
  2. If one operand is a subclass of `\GMP`, the subclass's method is invoked.
  3. If both operands are subclasses, only the left operand's method is called. Compatibility checks are the subclass's responsibility.

Here's a basic implementation example for duration represented in seconds:

  class Duration extends \GMP {
    public function __construct(int|\GMP|float $time) {
        parent::__construct($time, 10);
    }
 
    private static function assertCompatible($arg): void {
        if ($arg instanceof self || is_numeric($arg) || (is_object($arg) && get_class($arg) === '\GMP')) {
            return;
        }
        throw new LogicException('Duration is not compatible with ' . gettype($arg));
    }
 
    private static function assertNumber($arg): void {
        if (is_numeric($arg) || (is_object($arg) && get_class($arg) === '\GMP')) {
            return;
        }
        throw new LogicException('Argument is not a number: ' . gettype($arg));
    }
 
    protected static function add(mixed $left, mixed $right): static {
        self::assertCompatible($right);
        return new self(parent::add($left, $right));
    }
 
    protected static function subtract(mixed $left, mixed $right): static {
        self::assertCompatible($right);
        return new self(parent::subtract($left, $right));
    }
 
    protected static function multiply(mixed $left, mixed $right): static {
        if ($left instanceof self) self::assertNumber($right);
        if ($right instanceof self) self::assertNumber($left);
        return new self(parent::multiply($left, $right));
    }
 
    protected static function divide(mixed $left, mixed $right): static {
        self::assertNumber($right);
        return new self(parent::divide($left, $right));
    }
 
    protected static function mod(mixed $left, mixed $right): static {
        throw new LogicException('Not implemented');
    }
 
    protected static function exp(mixed $left, mixed $right): static {
        throw new LogicException('Not implemented');
    }
 
    protected static function comparable(mixed $left, mixed $right): bool {
        return $left instanceof self && $right instanceof self;
    }
 
    public function asMinutes(): float {
        return $this / 60;
    }
 
    /* and so on */
  }

This approach provides valuable semantics and type safety, especially in contexts like attributes and properties:

define('SECOND', new Duration(1));
 
// Use in attributes
#[Delay(5 * SECOND)]

// Use in properties
public Duration $delay = 5 * SECOND;

Backward Incompatible Changes

There are no backward incompatible changes. Existing GMP-based code will remain unaffected.

Proposed PHP Version(s)

8.4 if time allows, or the next version.

RFC Impact

To SAPIs

No impact.

To Existing Extensions

Only GMP will be affected.

To Opcache

There should be no impact to Opcache.

Open Issues

None, yet.

Unaffected PHP Functionality

Code using the GMP extension without extending it will remain unchanged.

Future Scope

  • support for other operators
  • serialization/unserialization

Proposed Voting Choices

  • Allow extending the \GMP class and use a form of operator overloading
  • Disallow extending the \GMP class

A “NO” vote implies making the `\GMP` class final to prevent further extensions.

Patches and Tests

A prototype patch will be provided before voting.

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature
  4. a link to the language specification section (if any)

References

Links to external references, discussions or RFCs

Rejected Features

Keep this updated with features that were discussed on the mail lists.

rfc/operator_overrides_lite.1719595296.txt.gz · Last modified: 2024/06/28 17:21 by withinboredom