Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:119044 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 19680 invoked from network); 28 Nov 2022 01:20:37 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 28 Nov 2022 01:20:37 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id B5F2B1804B5 for ; Sun, 27 Nov 2022 17:20:36 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE, SPF_PASS,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 27 Nov 2022 17:20:36 -0800 (PST) Received: by mail-pl1-f182.google.com with SMTP id jn7so8733412plb.13 for ; Sun, 27 Nov 2022 17:20:36 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=6ALqi8m0lYbOpVgjYJ9v2YZ0kSAlPtK5aBRegZRcYiE=; b=oBRDGuXMtxDlzZJ6oVU+eO791lSxJx5qjMslurpqZghGpZ3n0PNs0d9ym9SLUIQtDm r9wIqPI+f85XnefXLJyOO7wp/P6OpQJ0ZwdHJaq32Yymk/1u88N0lpdyBT0Vg90QrE24 fWUYP1o8bo5gC4S/d/+6VEBtTcpj224r+q5lZKY+m+19TSyKR1o/Yu8J2y6hikjg3DzJ lRO+aGMvesTQNJKTRRmrLsQ7RkpQwlxdR1A8DLxrBpOzY+3Xa23QMPcvcL6qLfU5dyu9 xIpCJ6s2ujSGdkoP+a57e4EM2vXBWPPt96Rq2UpdO9rFHG/I9OgeBjZ42yhqE55UqgOU csUw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=6ALqi8m0lYbOpVgjYJ9v2YZ0kSAlPtK5aBRegZRcYiE=; b=5HM726VP2lMXta+zP4rczjaIejr2oT83t7aQI8ZlPhvHk4BNosIHx7TNi6mvGf7Vo5 rP2VhgCnatclmdKsLsxkikDRuZIm1wvKGYe3iwlcTHuisgeTgbnsL0Y5txsy2ldFQVNa N0W28ObkJrcQRKFh07jjc4DvibJ7np8LiX7yGd6YGnKcJQTEMMDjcKXSiGXypf44wcX9 op4g7Za79All9p8tbsTXubDaN7crSe54HLt2ECVxa+6HzR+XYRfmx4dsTi950f2Fw6VM gJLIiGcoHgGr3KYDivC4bgE3Jw5bqU4RB0KrGLGwx3tA0lKmMAcNqliIEJbLQiVh8udc 2g0g== X-Gm-Message-State: ANoB5pmU2kygkRhciFf0ePpIrZuQfByWrLeIkillcQvkE8FoQRa9w0um nn/KUpwOnxaW9FiaxLAXMsJxtcuJAyhEuZVhbsY= X-Google-Smtp-Source: AA0mqf6V5jslHff+g6VFKjTxOLCkSwsxO3pbSjNp23OWqVdCZ1B+mWihiPJhEFiVu0spssdfhkUq//hu5iOtK1KKI74= X-Received: by 2002:a17:902:cf0e:b0:17c:5b01:f227 with SMTP id i14-20020a170902cf0e00b0017c5b01f227mr28945321plg.3.1669598435079; Sun, 27 Nov 2022 17:20:35 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: Date: Mon, 28 Nov 2022 01:20:23 +0000 Message-ID: To: Marco Pivetta Cc: =?UTF-8?B?TcOhdMOpIEtvY3Npcw==?= , PHP Internals List Content-Type: multipart/alternative; boundary="000000000000a3fb1405ee7dad74" Subject: Re: [PHP-DEV] [RFC] [Discussion] Readonly class amendments From: george.banyard@gmail.com ("G. P. B.") --000000000000a3fb1405ee7dad74 Content-Type: text/plain; charset="UTF-8" On Sat, 26 Nov 2022 at 23:06, Marco Pivetta wrote: > On Sat, 26 Nov 2022, 23:56 G. P. B., wrote: > >> As all the invariants will be maintained in the child classes >> > The invariant here is the immutability from a behavioural PoV. > LSP has 100% been broken in that example. > I've gone back to the example, and if we are going to use bad code to "argue" against certain features, then let's use this argument to its natural conclusion. But before that, we really need to get everyone clear on what LSP is. It is a subtyping relationship that states: if a property P on an object X of type T is provable to be true, then for an object Y of type S to be considered a proper subtype of T, this property P must also be true for all object Y of type S. Let's rephrase this slightly in terms more appropriate to PHP: if a characteristic P on an instance X of class T is provable to be true, then for an instance Y of class S to be considered a proper subclass of T, this characteristic P must also be true for all instances Y of class S. Having this in mind, let me show how easy it is to break LSP: class T { public function __construct(public readonly int $i) {} public function add(self $t): self { return new self($this->i + $t->i); } } $op1 = new T(5); $op2 = new T(14); $r = $op1->add($op2); var_dump($r); class S extends T { public function add(parent $t): self { return new self($this->i - $t->i); } } $op1 = new S(5); $op2 = new T(14); $r = $op1->add($op2); var_dump($r); Clearly, the characteristic that we can "add two T together" has been broken in class S. This is all to say, the type system can only provide a level of code *correctness*, and that remains even in highly sophisticated type systems. If you bear with me and the introduction of the following imaginary type dependent syntax: Matrix which declares a Matrix type which is an n by m matrix. Thus, we can write the signature of a function that does define matrix multiplication in the following way: function multiplication(Matrix $op1, Matrix $op2): Matrix Which would guarantee us that the 2 matrices provided can indeed be multiplied together as they have compatible dimensions, and it even gives us the dimensions of the output matrix. However, the one thing it cannot guarantee is that the operation we are actually doing is a matrix multiplication, I could just as well write a function that returns a new n by k matrix filled with zeroes. How can one be sure that a subclass will not alter the behaviour? By making the method final. That's mainly why personally I think final by default, and using composition over inheritance, is "better". And I think from what we've seen in language design over the last couple of decades is the OO inheritance is just a bad way to do subtyping and something like Haskell type-classes are way better. [1] But that ship has somewhat sailed for PHP. The LSP checks PHP (and for the matter any type system) can do and does, is checking the logical correctness of the implementation, not behavioural correctness that onus still remains on the developer. Now, let's have another look at your example: /* readonly */ class ImmutableCounter { public function __construct(private readonly int $count) {} public function add1(): self { return new self($this->count + 1); } public function value(): int { return $this->count; } } IMHO the fact that the count property is private is the biggest issue, just changing it to protected would solve most of the issues, as now you've given the tools to PHP to verify and ensure this constraint holds for the whole class hierarchy. But more importantly, it means I don't need to reimplement the *whole* parent class and (risk me messing up)/(keeping in sync) the behaviour of the parent class if I want to do something like: class SubstractableImmutableCounter extends ImmutableCounter { public function sub1(): self { return new self($this->count - 1); } } As it's easy to mess up even just writing a function properly that does the expected behaviour, I suppose we should remove the ability for users to write them, as they cannot be trusted with. So please, can we stop using the argument that this proposal breaks LSP if you write dumb code, as that's already the case, and it doesn't if you provide PHP with the actual information it needs to verify in child classes. Moreover, if one expects a readonly class to be immutable, then that is already not the case if one of the properties is a non-readonly object. It is also not necessarily stateless, as can be seen in the following example: https://3v4l.org/A2Lbs readonly class ImmutableBadNumberGenerator { public function __construct(private readonly int $i) {} public function generate(): int { static $j = 1; ++$j; return $this->i + $j; } } $o = new ImmutableBadNumberGenerator(10); var_dump($o->generate()); var_dump($o->generate()); var_dump($o->generate()); Now what *is* up to debate is what the expectation around a class extending a readonly class is. And that I don't really have strong opinions. However, what this discussion has told me more is that the expectation around readonly is more like the immutable keyword in D than the behaviour of the readonly keyword in other languages (as PHP and C# is the same AFAIK). Best regards, George P. Banyard [1] https://diogocastro.com/blog/2018/06/17/typeclasses-in-perspective/ --000000000000a3fb1405ee7dad74--