Re: Allow dropping typehints during inheritance

From: Date: Thu, 05 Feb 2015 23:11:23 +0000
Subject: Re: Allow dropping typehints during inheritance
References: 1 2 3 4 5 6  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message
On 5 February 2015 21:52:06 GMT, Levi Morrison <[email protected]> wrote:
>On Thu, Feb 5, 2015 at 5:14 AM, Andrea Faulds <[email protected]> wrote:
>> Hi Julien,
>>
>>> On 5 Feb 2015, at 12:10, Julien Pauli <[email protected]> wrote:
>>>
>>> If we allow larger type, why doesn't such code work ?
>>>
>>> interface A { }
>>> interface B extends A { }
>>>
>>> class C {
>>>     public function foo(A $a) { }
>>> }
>>>
>>> class D extends C {
>>>     public function foo(B $a) { } // E_STRICT
>>> }
>>>
>>> This is wrong IMO.
>>
>> Well, firstly that’s the wrong way round: inheriting classes can only
>*increase* the range of supported values, but you’ve done exactly the
>opposite.
>>
>> But even if you fixed your code, you’d still have an error. This
>ispresumably because doing anything other than simple invariance causes
>enormous problems related to compilation order and autoloading, as we
>discovered with the Return Types RFC.
>
>I would hardly call them "enormous problems". Just normal problems
>that I think were easier to avoid than to deal with given my schedule
>and the time-frame for PHP 7.
>
>To chime in regarding allowing contravariant parameter types: I
>struggle to find use cases for it. I can only think of one use-case
>and it's flawed: when something has declared an Iterator parameter and
>you widen it to include Traversable. However, even though Traversable
>is a parent of Iterator you can't directly call iterator methods on
>it, so it could break the calling code despite the super-type check
>passing.

There's nothing flawed about that example. Iterator adding methods to Traversable is a
competely normal thing for a subclass or subinterface to do. The *calling code* can only be broken
if a parameter it expected to work fails. If all you know about a method is that it expects an
Iterator, you'll give it an Iterator, and as long as an Iterator will actually work with the
implementation, then there is no way for it to break.

The contravariance is a convenience for the *implementer*, who can, unknown to the caller, write a
method which is also useful somewhere else. In this case, they might find they're only using
foreach on the argument; this will work fine for any Iterator, so the implementation is safe for the
existing caller. But somewhere else, they might find a use for their new implementation and want to
pass it a Traversable. Widening the type hint allows them to use the implementation in two different
contexts, competely safely.

The other example that I've mentioned a couple of times is an event dispatch system:

Say you have a base Event class, and sub-classes for LoginEvent, VoteEvent, etc. To listen for an
event, your listener has to implement an appropriate interface - LoginEventListener,
VoteEventListener, etc - which has a method definition handleEvent($event) with a type hint for the
corresponding Event class. This provides a nice two-way contract: the dispatcher knows that all
listeners will accept the Event objects it sends, and the listeners know that they will only get the
specific type of Event they are designed to listen to.

Now, imagine for debugging purposes you want to write a listener that logs the really basic details
of every event - things that every instance of Event will have, regardless of their subclass. You
want to register this for multiple events, which means implementing both LoginEventListener and
VoteEventListener, but they have contradictory type hints. Logically, you should be able to do so by
declaring your method as handleEvent(Event $event).

The dispatcher still knows, contractually, that when it gives you a LoginEvent, you'll accept
it; and you know that a LoginEvent is a sub-class of Event, so has the details you need.

Unfortunately, the language has to verify the relationship between various classes and interfaces to
confirm that this is in fact the case.

class Event { ... }
class LoginEvent extends Event { ... }
class VoteEvent extends Event { ... }

interface LoginEventListener { public function handleEvent(LoginEvent $event); }
interface VoteEventListener { public function handleEvent(VoteEvent $event); }

class DebugEventListener implements LoginEventListener, VoteEventListener {
public function handleEvent(Event $event) {
// Code using only methods and properties of the base Event class
}
}


Regards,
-- 
Rowan Collins
[IMSoP]



Thread (24 messages)

« previous php.internals (#81972) next »