On 5 February 2015 21:52:06 GMT, Levi Morrison <le...@php.net> wrote:
>On Thu, Feb 5, 2015 at 5:14 AM, Andrea Faulds <a...@ajf.me> wrote:
>> Hi Julien,
>>
>>> On 5 Feb 2015, at 12:10, Julien Pauli <jpa...@php.net> 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]


-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to