On Fri, Dec 17, 2021 at 9:43 AM Matt Fonda <matthewfo...@gmail.com> wrote:

> Hi Jordan,
>
> Thanks for the RFC. I have a couple questions:
>
> Suppose I have classes Foo and Bar, and I want to support the following
> operations:
>
> - Foo * Bar (returns Foo)
> - Bar * Foo (returns Foo)
>
> If I understand correctly, there are three possible ways I could implement
> this:
>
> a) Implement the * operator in Foo, accepting a Foo|Bar, and use the
> OperandPosition to determine if I am doing Foo * Bar or Bar * Foo and
> implement the necessary logic accordingly.
> b) Implement the * operator in Bar, accepting a Foo|Bar, and use the
> OperandPosition to determine if I am doing Foo * Bar or Bar * Foo and
> implement the necessary logic accordingly.
> c) Implement the * operator in Foo, accepting a Bar (handles Foo * Bar
> side); Implement the * operator in Bar, accepting a Foo (handles Bar * Foo
> side)
>
> Is this understanding correct? If so, which is the preferred approach and
> why? If not, can you clarify the best way to accomplish this?
>

You are correct in your understanding. All three of these would accomplish
what you want, but would have varying levels of maintainability. Which you
choose would depend on the specifics of the Foo and Bar class. For
instance, if the Bar class was one that you didn't ever expect to use on
its own with operators, only in combination with Foo, then it would make
sense to use option 1. The inverse would be true if Bar was the only one
you ever expected to use with operators on its own.

The better way, in general, would be for Foo and Bar to extend a common
class that implements the overload in the *same* way for both. In most
circumstances, (but not all), if you have two different objects used with
each other with operators, they should probably share a parent class or be
instances of the same class. Like I said, this isn't always true, but for
the majority of use cases I would expect it is.


> Next, suppose I also want to support int * Foo (returns int). To do this,
> I must implement * in Foo, which would look like one of the following
> (depending on which approach above)
>
> public operator *(Foo|int $other, OperandPos $pos): Foo|int { ... }
> public operator *(Foo|Bar|int $other, OperandPos $pos): Foo|int { ... }
>
> Now, suppose I have an operation like `42 * $foo`, which as described
> above, should return int. It seems it is not possible to enforce this via
> typing, is that correct? i.e. every time I use this, I am forced to do:
>
> $result = 42 * $foo;
> if (is_int($result)) {
>     // can't just assume it's an int because * returns Foo|int
> }
>

In general I would say that returning a union from an operator overload is
a recipe for problems. I would either always return an int, or always
return an instance of the calling class. Mostly, this is because any scalar
can be easily represented with a class as well.

Jordan

Reply via email to