On Sat, Aug 14, 2021 at 3:44 PM Jordan LeDoux <jordan.led...@gmail.com>
wrote:

>
>
> On Sat, Aug 14, 2021 at 6:25 AM Nikita Popov <nikita....@gmail.com> wrote:
>
>>
>> function addMultiple(CollectionInterface $collection, mixed ...$inputs):
>> void {
>>     foreach ($inputs as $input) $collection->add($input);
>> }
>>
>> A static analyzer should flag this CollectionInterface::add() call as
>> invalid, because mixed is passed to never. Effectively, this means that an
>> interface using never argument types cannot actually be used in anything
>> *but* inheritance -- so what is its purpose?
>>
>>
> When used as a sort of... pseudo-generics replacement, you'd need to use
> Docblocks to specify these, because **this feature is not generics** (which
> you correctly pointed out). I probably should have made that MORE clear so
> as to not confuse or trick anyone.
>
> If this RFC were passed, it could be sort of used like generics but it
> would be a bit hacky to use it that way as your example illustrates. In the
> absence of generics, this would probably be used as a stopgap in
> combination with docblocks. That's the point I was trying to make. :)
>
> The main value I see from an inheritance perspective is using never to
> disallow an omitted type. The inheriting class may specify *any* type, even
> mixed, but it must do so explicitly.
>

I don't think this really addresses my concern, so let me repeat it: You
cannot actually call a method using a never-type argument while typing
against the interface. What's the point of the interface then?

I don't think "you must use this in conjunction with a 3rd-party phpdoc
generics implementation for it to make any sense at all" is a suitable way
to resolve that.

The only case where this is somewhat sensible is with interfaces
controlling engine behavior, like the operator overloading interfaces
Addable etc you clearly have in mind here. If you never actually type
against them and only use them as a marker for the engine, then this works
out fine. But this still leaves the interface useless from a typesystem
perspective, it merely becomes an engine marker.

We have a better way to specify markers for the engine: Magic methods. I
think some people have a mistaken notion that engine-integrated interfaces
are always better than magic methods. This is only the case if such
interfaces are actually useful from a type system perspective. For example,
Countable and Traversable are useful magic interfaces, because you can
sensibly type against them. The recently introduced Stringable interface
(for the magic method __toString) falls in the same category.

Conversely, Serializable is actively harmful as a magic interface (apart
from the other issues with it), because whether an class implements
Serializable does not determine whether it is serializable -- all objects
are a priori serializable, the Serializable interface just gives it custom
serialization behavior. You'll note that the new __serialize/__unserialize
methods are plain magic methods without an interface. With exception of a
custom serializer implementation, user code should never be checking for
Serializable.

The operator overloading case is in between: The interface is not actively
harmful, but they also aren't useful. Given the lack of generics, it's not
really possible to write code against a "Multiplyable" interface that
actually provides a useful guarantee. The interface does not distinguish
whether "T * T" is valid, or only scalar multiplication "T * float" is
supported. When working with operator overloads in PHP, I expect usage to
type against a specific class implementing operator overloading, say Money,
rather than typing against something like Addable&Multiplyable. The latter
would accept both Money and Matrix, both of which have entirely different
rules on the kinds of operands they accept, even if they are, in some
sense, addable and multiplyable.

Regards,
Nikita

Reply via email to