Hi internals!
Currently we do not allow [1] removing a typehint during inheritance. For
example the following code is not valid:
interface A {
public function method(Typehint $param);
}
class B implements A {
public function method($param);
}
// Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method()
accepts more inputs than A::method(). However we still forbid it.
This is an issue, because it makes it impossible to add typehints to
parameters at a later point in time. I've seen this issue come up both in
userland code, as well as in a recent DateTime change, see
https://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47
.
Instead of reverting the DateTime BC break, I'm wondering if it wouldn't be
better to fix the root cause by making the inheritance check less strict
and allow removing typehints?
Thanks,
Nikita
[1] This is a fatal error when interfaces or abstract methods are involved,
otherwise this is an E_STRICT
error.
Hi Nikita,
Currently we do not allow [1] removing a typehint during inheritance. For
example the following code is not valid:interface A {
public function method(Typehint $param);
}
class B implements A {
public function method($param);
}
// Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)The above code does not constitute an LSP violation, because B::method()
accepts more inputs than A::method(). However we still forbid it.This is an issue, because it makes it impossible to add typehints to
parameters at a later point in time. I've seen this issue come up both in
userland code, as well as in a recent DateTime change, see
https://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47
.Instead of reverting the DateTime BC break, I'm wondering if it wouldn't be
better to fix the root cause by making the inheritance check less strict
and allow removing typehints?
Sounds sensible to me.
Of course, the reverse is true for return types, which should be either covariant or invariant. :)
Andrea Faulds
http://ajf.me/
-----Ursprüngliche Nachricht-----
Von: Nikita Popov [mailto:nikita.ppv@gmail.com]
Gesendet: Mittwoch, 4. Februar 2015 19:50
An: PHP internals
Betreff: [PHP-DEV] Allow dropping typehints during inheritanceHi internals!
Currently we do not allow [1] removing a typehint during inheritance. For example the following code is not valid:
interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible with A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method() accepts more inputs than A::method().
However we still forbid it.This is an issue, because it makes it impossible to add typehints to parameters at a later point in time. I've seen this issue
come up both in userland code, as well as in a recent DateTime change, see
https://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47
.Instead of reverting the DateTime BC break, I'm wondering if it wouldn't be better to fix the root cause by making the
inheritance check less strict and allow removing typehints?Thanks,
Nikita[1] This is a fatal error when interfaces or abstract methods are involved, otherwise this is an
E_STRICT
error.
I do not entirely agree here since what you are proposing is not to introduce contravariance for parameters (which I would support) but remove the check entirely.
In this sense it is an LSP violation (you cannot substitute A with B and guarantee the correctness) and therefore should not be introduced into PHP.
Cheers,
Robert
-----Ursprüngliche Nachricht-----
Von: Robert Stoll [mailto:php@tutteli.ch]
Gesendet: Mittwoch, 4. Februar 2015 20:24
An: 'Nikita Popov'; 'PHP internals'
Betreff: AW: [PHP-DEV] Allow dropping typehints during inheritance-----Ursprüngliche Nachricht-----
Von: Nikita Popov [mailto:nikita.ppv@gmail.com]
Gesendet: Mittwoch, 4. Februar 2015 19:50
An: PHP internals
Betreff: [PHP-DEV] Allow dropping typehints during inheritanceHi internals!
Currently we do not allow [1] removing a typehint during inheritance. For example the following code is not valid:
interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method() accepts more inputs than A::method().
However we still forbid it.This is an issue, because it makes it impossible to add typehints to
parameters at a later point in time. I've seen this issue come up both
in userland code, as well as in a recent DateTime change, see
https://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00
169fea47
.Instead of reverting the DateTime BC break, I'm wondering if it
wouldn't be better to fix the root cause by making the inheritance check less strict and allow removing typehints?Thanks,
Nikita[1] This is a fatal error when interfaces or abstract methods are involved, otherwise this is an
E_STRICT
error.I do not entirely agree here since what you are proposing is not to introduce contravariance for parameters (which I would
support) but remove the check entirely.
In this sense it is an LSP violation (you cannot substitute A with B and guarantee the correctness) and therefore should not
be introduced into PHP.Cheers,
Robert--
I have to correct myself, without type hint is equal to say any type and in this sense it is contravariance, all good to go :)
Nikita Popov wrote on 04/02/2015 18:49:
Hi internals!
Currently we do not allow [1] removing a typehint during inheritance. For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method()
accepts more inputs than A::method(). However we still forbid it.
For those that missed it, or have forgotten, there was a discussion of
covariance and contravariance in November, kicked off by Levi's concerns
over return type hints:
http://grokbase.com/t/php/php-internals/14bstfz0pg/rfc-discussion-return-type-variance-checking
In particular, this Wikipedia article explains the concepts very well
(although once you get into generics it all gets a bit complicated, and
irrelevant to us right now):
http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Covariant_method_return_type
Removing the typehint is a special case of contravariance, because it
means "accept any type", and parameter contravariance in a nutshell is
"a child implementation must accept at least what the parent accepts,
but may also accept more".
The current situation (invariance) is quite intuitive - the signatures
must match - and full contravariance can be explained fairly easily -
"B:method must accept all values accepted by A::method". My worry is
that people won't understand why this particular special case is
allowed, and may think PHP is just letting them be sloppy and remove
other people's type hints.
The other thing to consider is whether the same special case should be
added (in reverse) to return type hints - a sub-class could introduce
a return type hint, as long as the parent class did not already declare one.
Regards,
Rowan Collins
[IMSoP]
On Wed, Feb 4, 2015 at 9:04 PM, Rowan Collins rowan.collins@gmail.com
wrote:
Nikita Popov wrote on 04/02/2015 18:49:
Hi internals!
Currently we do not allow [1] removing a typehint during inheritance. For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method()
accepts more inputs than A::method(). However we still forbid it.For those that missed it, or have forgotten, there was a discussion of
covariance and contravariance in November, kicked off by Levi's concerns
over return type hints: http://grokbase.com/t/php/php-
internals/14bstfz0pg/rfc-discussion-return-type-variance-checkingIn particular, this Wikipedia article explains the concepts very well
(although once you get into generics it all gets a bit complicated, and
irrelevant to us right now): http://en.wikipedia.org/wiki/
Covariance_and_contravariance_(computer_science) <
http://en.wikipedia.org/wiki/Covariance_and_contravariance_
%28computer_science%29#Covariant_method_return_type>Removing the typehint is a special case of contravariance, because it
means "accept any type", and parameter contravariance in a nutshell is "a
child implementation must accept at least what the parent accepts, but may
also accept more".The current situation (invariance) is quite intuitive - the signatures
must match - and full contravariance can be explained fairly easily -
"B:method must accept all values accepted by A::method". My worry is that
people won't understand why this particular special case is allowed, and
may think PHP is just letting them be sloppy and remove other people's type
hints.The other thing to consider is whether the same special case should be
added (in reverse) to return type hints - a sub-class could introduce a
return type hint, as long as the parent class did not already declare one.
Whatever we choose, we must find a solution that is unique and spread into
all the core language and core exts.
And we have to think about BC : if we choose something more strict, we will
generate more BC breaks for the switch to PHP7, which is not really a good
thing.
However, I'm all good for us to find a clear solution on type parameters,
return types, extended type hints ... whatever other RFC will be accepted
about this kind of subject.
PHP7 should also be the moment we turn PHP into a more consistent, mature
and professional language.
And on the way to make the right choice, why not start by having a look at
what other languages provide ?
Julien Pauli
Hi Nikita,
Currently we do not allow [1] removing a typehint during inheritance. For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method()
accepts more inputs than A::method(). However we still forbid it.This is an issue, because it makes it impossible to add typehints to
parameters at a later point in time. I've seen this issue come up both in
userland code, as well as in a recent DateTime change, seehttps://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47
.Instead of reverting the DateTime BC break, I'm wondering if it wouldn't be
better to fix the root cause by making the inheritance check less strict
and allow removing typehints?
I can understand your reason. It's reasonable perfectly.
Template is better, but PHP is weakly typed language.
I think it's acceptable.
Since Dmitry agreed to introduce DbC, if he like the syntax, etc and
proposal is passed.
DbC may be used to check various types or user may simply write code that
handles
various types in function body.
Regards,
--
Yasuo Ohgaki
yohgaki@ohgaki.net
Hi Nikita,
On Thu, Feb 5, 2015 at 3:49 AM, Nikita Popov nikita.ppv@gmail.com
wrote:Currently we do not allow [1] removing a typehint during inheritance.
For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible
with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because
B::method()
accepts more inputs than A::method(). However we still forbid it.This is an issue, because it makes it impossible to add typehints to
parameters at a later point in time. I've seen this issue come up
both in
userland code, as well as in a recent DateTime change, seehttps://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47
.
Instead of reverting the DateTime BC break, I'm wondering if it
wouldn't be
better to fix the root cause by making the inheritance check less
strict
and allow removing typehints?I can understand your reason. It's reasonable perfectly.
Template is better, but PHP is weakly typed language.
I think it's acceptable.Since Dmitry agreed to introduce DbC, if he like the syntax, etc and
proposal is passed.
DbC may be used to check various types or user may simply write code
that
handles
various types in function body.
This is actually my fear: that people will misunderstand this as an excuse to write invalid type checks, such as ones which are tighter rather than looser than the parent class (it takes a bit of careful thought to understand why this is wrong). Unfortunately, we don't have a keyword for explicitly allowing any value, or even a base for all objects, so I guess we have to take that risk, but at least if contravariance were fully supported, we could have clear examples of correct usage.
(Incidentally, I'm not sure how templates are relevant - this is purely about inheritance and LSP of ordinary objects.)
--
Rowan Collins
[IMSoP]
On Wed, Feb 4, 2015 at 10:17 PM, Rowan Collins rowan.collins@gmail.com
wrote:
Hi Nikita,
On Thu, Feb 5, 2015 at 3:49 AM, Nikita Popov nikita.ppv@gmail.com
wrote:Currently we do not allow [1] removing a typehint during inheritance.
For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible
with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because
B::method()
accepts more inputs than A::method(). However we still forbid it.This is an issue, because it makes it impossible to add typehints to
parameters at a later point in time. I've seen this issue come up
both in
userland code, as well as in a recent DateTime change, seehttps://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47
.
Instead of reverting the DateTime BC break, I'm wondering if it
wouldn't be
better to fix the root cause by making the inheritance check less
strict
and allow removing typehints?I can understand your reason. It's reasonable perfectly.
Template is better, but PHP is weakly typed language.
I think it's acceptable.Since Dmitry agreed to introduce DbC, if he like the syntax, etc and
proposal is passed.
DbC may be used to check various types or user may simply write code
that
handles
various types in function body.This is actually my fear: that people will misunderstand this as an excuse
to write invalid type checks, such as ones which are tighter rather than
looser than the parent class (it takes a bit of careful thought to
understand why this is wrong). Unfortunately, we don't have a keyword for
explicitly allowing any value, or even a base for all objects, so I guess
we have to take that risk, but at least if contravariance were fully
supported, we could have clear examples of correct usage.(Incidentally, I'm not sure how templates are relevant - this is purely
about inheritance and LSP of ordinary objects.)
Maybe I wasn't clear here. I do not propose to allow any code that violates
LSP. You will not be able to add a parameter typehint if the parent doesn't
have one and you will not be able to change a typehint to a different
class. I'm only proposing to support parameter contravariance to the
limited degree we are capable of implementing currently.
Nikita
On Wed, Feb 4, 2015 at 10:17 PM, Rowan Collins
rowan.collins@gmail.com
wrote:On 4 February 2015 21:02:30 GMT, Yasuo Ohgaki yohgaki@ohgaki.net
wrote:Hi Nikita,
On Thu, Feb 5, 2015 at 3:49 AM, Nikita Popov nikita.ppv@gmail.com
wrote:Currently we do not allow [1] removing a typehint during
inheritance.
For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible
with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because
B::method()
accepts more inputs than A::method(). However we still forbid it.This is an issue, because it makes it impossible to add typehints
to
parameters at a later point in time. I've seen this issue come up
both in
userland code, as well as in a recent DateTime change, seehttps://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47
.
Instead of reverting the DateTime BC break, I'm wondering if it
wouldn't be
better to fix the root cause by making the inheritance check less
strict
and allow removing typehints?I can understand your reason. It's reasonable perfectly.
Template is better, but PHP is weakly typed language.
I think it's acceptable.Since Dmitry agreed to introduce DbC, if he like the syntax, etc and
proposal is passed.
DbC may be used to check various types or user may simply write code
that
handles
various types in function body.This is actually my fear: that people will misunderstand this as an
excuse
to write invalid type checks, such as ones which are tighter rather
than
looser than the parent class (it takes a bit of careful thought to
understand why this is wrong). Unfortunately, we don't have a keyword
for
explicitly allowing any value, or even a base for all objects, so I
guess
we have to take that risk, but at least if contravariance were fully
supported, we could have clear examples of correct usage.(Incidentally, I'm not sure how templates are relevant - this is
purely
about inheritance and LSP of ordinary objects.)Maybe I wasn't clear here. I do not propose to allow any code that
violates
LSP. You will not be able to add a parameter typehint if the parent
doesn't
have one and you will not be able to change a typehint to a different
class. I'm only proposing to support parameter contravariance to the
limited degree we are capable of implementing currently.
I was replying to the idea that a "user may simply write code that handles various types in function body". While a savvy user would only use that to implement contravariance, encouraging it as a possibility risks people implementing covariance or other weirdness because they haven't understood why it's a bad thing to do.
Full support for contravariance wouldn't stop that being possible, but it would give much better examples for people to learn from.
Regards,
Rowan Collins
[IMSoP]
Hi internals!
Currently we do not allow [1] removing a typehint during inheritance. For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method()
accepts more inputs than A::method(). However we still forbid it.
So what it supports "more inputs"?
It does constitute an LSP violation. "more inputs" is not what the
guarantee is at all, if that is what you want you'd typehint on a
interface.
It is a LSP failure to allow a string, or any other scalar value, when
the parent requires a specific type/object.
It sucks that we fail our arginfo very frequently, but this is the way it is :]
-Hannes
Hi Hannes,
So what it supports "more inputs"?
It does constitute an LSP violation. "more inputs" is not what the
guarantee is at all, if that is what you want you'd typehint on a
interface.It is a LSP failure to allow a string, or any other scalar value, when
the parent requires a specific type/object.It sucks that we fail our arginfo very frequently, but this is the way it is :]
An interface requires only a minimum standard for accepted input. If a class implementing that interface allows a wider range of values, but still allows at least what the interface requires it to, it is not an LSP violation. Scalars are not special.
Andrea Faulds
http://ajf.me/
Hi Hannes,
On 4 Feb 2015, at 23:58, Hannes Magnusson hannes.magnusson@gmail.com
wrote:So what it supports "more inputs"?
It does constitute an LSP violation. "more inputs" is not what the
guarantee is at all, if that is what you want you'd typehint on a
interface.It is a LSP failure to allow a string, or any other scalar value, when
the parent requires a specific type/object.It sucks that we fail our arginfo very frequently, but this is the way
it is :]An interface requires only a minimum standard for accepted input. If a
class implementing that interface allows a wider range of values, but still
allows at least what the interface requires it to, it is not an LSP
violation. Scalars are not special.
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.
Julien.P
Hi Hannes,
On 4 Feb 2015, at 23:58, Hannes Magnusson hannes.magnusson@gmail.com
wrote:So what it supports "more inputs"?
It does constitute an LSP violation. "more inputs" is not what the
guarantee is at all, if that is what you want you'd typehint on a
interface.It is a LSP failure to allow a string, or any other scalar value, when
the parent requires a specific type/object.It sucks that we fail our arginfo very frequently, but this is the way
it is :]An interface requires only a minimum standard for accepted input. If a
class implementing that interface allows a wider range of values, but still
allows at least what the interface requires it to, it is not an LSP
violation. Scalars are not special.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.
This is not "larger" but more specific.
--
Regards,
Mike
Hi Julien,
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 think that we’d probably support full contravariance for parameters if we could.
However, we could easily support contravariance in type hint existence, as that doesn’t require subclassing checks.
--
Andrea Faulds
http://ajf.me/
Hi Julien,
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.
To chime in regarding allowing contravariant parameter types: I
struggle to find use cases for it.
Theoretically it would allow a class to implement two separate
interfaces that would otherwise be incompatible:
interface A {
function foo();
}
interface B implements A {
function bar(B $b);
}
interface C implements A {
function bar(C $c);
};
class D implements B, C {
// Any object that implements B or C must also
// implement A, but only the methods from interface A
// are safe to call inside the method.
function bar(A $a){
$a->foo();
}
function foo(){...}
}
But I agree, the number of times I've wanted to do this is low.
cheers
Dan
Hi Julien,
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]
-----Ursprüngliche Nachricht-----
Von: julienpauli@gmail.com [mailto:julienpauli@gmail.com] Im Auftrag von Julien Pauli
Gesendet: Donnerstag, 5. Februar 2015 13:10
An: Andrea Faulds
Cc: Hannes Magnusson; Nikita Popov; PHP internals
Betreff: Re: [PHP-DEV] Allow dropping typehints during inheritanceHi Hannes,
On 4 Feb 2015, at 23:58, Hannes Magnusson
hannes.magnusson@gmail.com
wrote:So what it supports "more inputs"?
It does constitute an LSP violation. "more inputs" is not what the
guarantee is at all, if that is what you want you'd typehint on a
interface.It is a LSP failure to allow a string, or any other scalar value,
when the parent requires a specific type/object.It sucks that we fail our arginfo very frequently, but this is the
way
it is :]An interface requires only a minimum standard for accepted input. If a
class implementing that interface allows a wider range of values, but
still allows at least what the interface requires it to, it is not an
LSP violation. Scalars are not special.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.
Julien.P
Because this would be covariance and violates LSP. Considering the following:
function foo(C $c){
$c->foo(new A);
}
if you substitute C with D then suddenly the code would not work anymore.
Cheers,
Robert
Robert Stoll wrote on 05/02/2015 12:16:
-----Ursprüngliche Nachricht-----
Von: julienpauli@gmail.com [mailto:julienpauli@gmail.com] Im Auftrag von Julien Pauli
Gesendet: Donnerstag, 5. Februar 2015 13:10
An: Andrea Faulds
Cc: Hannes Magnusson; Nikita Popov; PHP internals
Betreff: Re: [PHP-DEV] Allow dropping typehints during inheritanceHi Hannes,
On 4 Feb 2015, at 23:58, Hannes Magnusson
hannes.magnusson@gmail.com
wrote:
So what it supports "more inputs"?
It does constitute an LSP violation. "more inputs" is not what the
guarantee is at all, if that is what you want you'd typehint on a
interface.It is a LSP failure to allow a string, or any other scalar value,
when the parent requires a specific type/object.It sucks that we fail our arginfo very frequently, but this is the
way
it is :]An interface requires only a minimum standard for accepted input. If a
class implementing that interface allows a wider range of values, but
still allows at least what the interface requires it to, it is not an
LSP violation. Scalars are not special.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.
Because this would be covariance and violates LSP. Considering the following:
function foo(C $c){
$c->foo(new A);
}if you substitute C with D then suddenly the code would not work anymore.
Indeed. The contract states that if you know you have an instance of C,
you can pass any instance of A to its foo method. If you are given an
instance of D, that contract will be violated, because instances of A
which are not instances of B will be rejected.
The other way around, however, is theoretically fine (though not
implemented in PHP because its complicated to check):
class X { public function bar(B $thing) {} }
class Y extends X { public function bar(A $thing) {} }
Here, the contract states that if you have an instance of X, you can
pass any instance of B to its bar method. If you are given an instance
of Y, you can still pass any B to its bar method, so the contract is
met. If you /know/ that you have an instance of Y, you know you can also
pass other types of A//.
A real-life example that came to mind previously was an event handler
interface that told implementers exactly what kind of event to expect,
being implemented by a logger that can actually accept any kind of event
you give it. As long as the event dispatchers knows that the logger
accepts the event its dispatching, it doesn't care what else it can do.
Regards,
Rowan Collins
[IMSoP]
Hi!
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.
This shouldn't work - it means that if you have D object, you should be
able to call foo on it with any A parameter. However, your definition
says not every A parameter would work, but only one that is actually B
(i.e. class that implements A but not B is not going to work anymore).
That means D violates C's contract by rejecting some calls that C accepted.
--
Stas Malyshev
smalyshev@gmail.com
Hannes Magnusson wrote on 04/02/2015 23:58:
Hi internals!
Currently we do not allow [1] removing a typehint during inheritance. For
example the following code is not valid:interface A { public function method(Typehint $param); } class B implements A { public function method($param); } // Fatal error: Declaration of B::method() must be compatible with
A::method(Typehint $param)
The above code does not constitute an LSP violation, because B::method()
accepts more inputs than A::method(). However we still forbid it.
So what it supports "more inputs"?
It does constitute an LSP violation. "more inputs" is not what the
guarantee is at all, if that is what you want you'd typehint on a
interface.
Have a look at the Wikipedia article on covariance and contravariance;
the idea is that if you know you have a container which can contain
Cats, a custom implementation that also happens to accept Dogs is fine,
but an implementation that only accepts Kittens is not, because the
object is contractually required to accept any Cat that you give it.
Return types are the reverse, because the object is contractually
required to return you nothing but Cats, so returning a Dog would be a
violation; but constraining itself to only return Kittens doesn't affect
the contract you have with it.
The special case proposed here is that the lack of type hint means "any
type", as though you were type hinting the base of all possible types.
Thus removing a typehint on a parameter does not allow the object to
violate the parent's contract, and nor does adding a return typehint.
However, the fact that this is tricky to understand is probably a big
problem for it. The Wikipedia article notes that few languages implement
parameter contravariance, mostly because they'd have to mix it with
type-based function overloading, making real-life examples hard to find.
Regards,
Rowan Collins
[IMSoP]
Hi,
I’m coming back to this thread after some discussions about related matters off-list. I’m firmly convinced this is a good idea, now:
- It would be very useful to extend the callable type hint to support typed parameters and return types. If we were to do so, its validation rules should follow inheritance’s. It would be really unfortunate if you couldn’t pass an untyped callback to a callable with typed parameters.
- With the Scalar Type Hints RFCs, and other future RFCs, more type hints may be added to the language. It’d be a shame if existing interfaces couldn’t have type hints added to them.
- Contravariance is widely accepted as being sound for parameter types.
- We already have covariance for return type existence. So why not contravariance for parameter type existence?
I hope we can have this added.
--
Andrea Faulds
http://ajf.me/
If you consider the lack of an explicit type to mean the theoretical
"Any" type which is a supertype of all types then dropping the hint is
a form of contravariance. I would prefer to support normal
contravariance for parameter types in which this is just a special
case. However, I am not sure I would be against it without regular
contravariance support if it came to a vote.