Re: Protected inheritance hierarchies
| From: | Rob Landers | Date: | Fri, 08 Aug 2025 14:03:56 +0000 |
| Subject: | Re: Protected inheritance hierarchies | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-128436@lists.php.net to get a copy of this message | ||
On Fri, Aug 8, 2025, at 15:11, Hans Krentel wrote:
>
>
>
> On Friday 08 August 2025 00:49:27 (+02:00), Morgan wrote:
>
> > On 2025-08-08 10:01, Rob Landers wrote:
> > > For example, if |A::foo(): int| promises to always return a non-zero integer, and a
> > > subclass overrides |foo()| to only return zero, that violates the contract—even though the type
> > > signatures match perfectly. This is the sort of semantic guarantee that LSP is about, and it is
> > > discussed in nearly every reputable textbook and conference talk on OO design. Languages like PHP
> > > can’t enforce these contracts, but the / principle/ is what helps prevent subtle design bugs and
> > > behavioural “surprises.”
> > > Indeed. Those contracts only become enforceable in the type signature when your type
> > > system is robust enough to be able to express them. If you could declare |A::foo(): NonzeroInt| then
> > > the signature could prevent an overriding subclass C from returning zero from that function (while
> > > still allowing |C::foo(): PositiveInt|, say); the compiler can verify that the type of the
> > > expression C::foo() is written to return is of type NonzeroInt (or PositiveInt).
> >
> > In the absence of being able to declare such constraints in the type system, one has to
> > fall back on other documentation about what behaviour is and isn't acceptable.
> >
>
> That is certainly always useful, especially when it was written down, as it allows to read
> both, the fine-print, and between the lines. As you have just quoted Rob's suggestion while
> replying to it, allow me the opportunity to highlight a key sentence for me under the pretext of the
> earlier quote, and the much appreciated association with documentation of yours to illustrate that:
>
> > [...] the / principle/ [here, LSP,] is what helps prevent subtle design bugs and
> > behavioural “surprises.”
>
> Documentation, e.g. of pre- and postconditions, loop (in)variants, class invariants, etc, we
> can already make use of those corner-stones of the LSP to reason about sub-types, the LSP can
> inspire us of what or how we document. And that's what I've learned these days from Rob:
> Without reasoning about the LSP. I knew already for what I love Liskov for, but the LSP is so much a
> loaded thing in discussions, I had to get a couple of things out of the way first to only understand
> Robs reasoning. I came here by the error message, and was looking for what I was missing with it.
>
> "Oh the types in PHP must do that for us, otherwise my scripts are doomed."
>
> No.
>
> The compiler can only handle the type but not the sub-type, because of the halting problem.
>
> And while my programs can be doomed because of the halting problem (4. Every program is a part
> of some other program and rarely fits.), I as human am still able to reason about the correctness of
> my program.
>
> Just my 2 cents
>
> -- hakre
To add to this, here’s one of my favorite examples, ironically, one often used in beginner OOP
tutorials:
class Rectangle {
public int $width;
public int $height;
}
class Square extends Rectangle {
public int $width { get => $this->width; set => $this->width = $this->height =
$value; }
public int $height { get => $this->height; set => $this->height = $this->width =
$value; }
}
This is a classic LSP violation. Square changes Rectangle's contract by linking width/height,
removing the independence that Rectangle promised. Meaning when we pass it as a Rectangle and we try
to make the Square full screen, it will either be too short length (set width first) or too tall
(set height first). In other words, a "Square is a Rectangle" violates LSP, in practice.
Yet, this is quite often taught as a beginner example to OOP.
Are there cases where you'd want to do this deliberately? Yes, probably. Would you be surprised
to find a rectangle extended to the edge of the screen did not extend to the edge of the screen?
Yes, probably. In fact, it would probably be filed as a bug.
Interestingly, your product people will tell you to "stretch" it (or perform some other
transform) making a Square behave like a Rectangle again.. You'd probably end up with something
more akin to this:
class Rectangle {
public int $width;
public int $height;
public static function asSquare(int $widthAndHeight): static { /* set width and height */ }
}
Anyway, I digress. Software design is fun... it's a great time to be alive.
— Rob