Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:114142 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 63660 invoked from network); 25 Apr 2021 03:44:36 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 25 Apr 2021 03:44:36 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id C16301804C6 for ; Sat, 24 Apr 2021 20:48:15 -0700 (PDT) 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.6 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-Virus: No X-Envelope-From: Received: from wout2-smtp.messagingengine.com (wout2-smtp.messagingengine.com [64.147.123.25]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sat, 24 Apr 2021 20:48:15 -0700 (PDT) Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailout.west.internal (Postfix) with ESMTP id AA27E12AA for ; Sat, 24 Apr 2021 23:48:13 -0400 (EDT) Received: from imap8 ([10.202.2.58]) by compute4.internal (MEProxy); Sat, 24 Apr 2021 23:48:13 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=content-transfer-encoding:content-type :date:from:in-reply-to:message-id:mime-version:references :subject:to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender :x-sasl-enc; s=fm2; bh=juaK3WHm8jB262aj+Eo5o0uR6eAB8cyJYaiNs9oWU kc=; b=Mk3aVJ6yKMPunFYQ6zkynMWIzEMHCQbLRfbQZqSpwQRQdlf/u4BCv8xqs 8YbjVKMQc0YZl31hftEcc5Mnt9q0vp8sPeMjflUkJePazeauNddoS6HZerCZutX2 d/r7wOtxEYXGIQ9NKrXJdbV4MkatHEnzImuWiLyQ34wNIGymIqch4oV16llDQjpk s3hpF4muvT9m/8Z56wH3opMOOWK/hKZZToEXKH44MNOZKspWceXhpyRQTsFBlkpH 4GXb94j+ZiFR7cbwr3TxfpuIvL4fBxAjPpfJ5Z+o4LoBCQqKFchErkIVRm5sfuNU AwWjZe7qqKlKRraZbiyZXkkFyYAJA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeduledrvdduhedgieekucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhepofgfggfkjghffffhvffutgfgsehtqhertderreejnecuhfhrohhmpedfnfgr rhhrhicuifgrrhhfihgvlhgufdcuoehlrghrrhihsehgrghrfhhivghlughtvggthhdrtg homheqnecuggftrfgrthhtvghrnhepgeeghefgteejheeggfeghfelueeggfdtjeeivedv tefhveeguedufeelhedvteeinecuffhomhgrihhnpehphhhprdhnvghtnecuvehluhhsth gvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplhgrrhhrhiesghgrrhhf ihgvlhguthgvtghhrdgtohhm X-ME-Proxy: Received: by mailuser.nyi.internal (Postfix, from userid 501) id 2CE0E3A05A1; Sat, 24 Apr 2021 23:48:13 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.5.0-alpha0-403-gbc3c488b23-fm-20210419.005-gbc3c488b Mime-Version: 1.0 Message-ID: In-Reply-To: References: <5b9f1500-615a-48f1-815f-1d48b327ef90@processus.org> <179049b1475.11134368b213512.254739612773841999@void.tn> Date: Sat, 24 Apr 2021 22:47:52 -0500 To: "php internals" Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] [RFC][Draft] Sealed Classes From: larry@garfieldtech.com ("Larry Garfield") On Sat, Apr 24, 2021, at 2:55 PM, Olle H=C3=A4rstedt wrote: > 2021-04-24 21:51 GMT+02:00, Marco Pivetta : > > On Sat, Apr 24, 2021, 21:44 Olle H=C3=A4rstedt wrote: > > > >> 2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati : > >> >> Doesn't this violate the principle: It should be possible to add= new > >> >> features without touching old code? > >> > > >> > This depends on which syntax is picked, both `for` and attribute = syntax > >> will > >> > be completely BC. > >> > >> I'm not talking about BC, but the maintainability of the new featur= e > >> itself. For the shape example, you'd need to edit the original file= > >> for each new shape you add, which is detrimental for maintainabilit= y > >> and scalability. So what's a good use-case? > >> > > > > The main use-case of sealed types is being able to declare total fun= ctions > > around them. >=20 > What is "total function" in your discourse? :) Can you find a more > concrete example? Preferably one that's relevant for web site/app > development. Shapes is a bit too generic, I think. >=20 > Olle A total function is a function that is defined over the entire domain of= its inputs. For example, addition is a total function over integers, b= ecause for every possible pair of integers you pass to it there is a log= ical return value. However, square root is not a total function over in= tegers because there are some integers you pass it for which there is no= t representable return value. (Negative numbers, unless you get into im= aginary numbers which PHP doesn't support.) In those cases, you have to= throw an exception or return an error code or similar. For a more typical PHP example, getUser(int $id) is not a total function= , unless you have PHP_MAX_INT user objects defined in your database. If= you pass an int that does not correspond to a defined user, you now hav= e to deal with "user not found" error handling. getUser() is not a tota= l function. getUsers(array $criteria), however, arguably is, because it= 's logical and reasonable to map all not-found cases to an empty array/c= ollection, which doesn't require any special error handling. In practice, I think all of the use cases for sealed classes are ADT-esq= ue. As I noted before, combining sealed classes with Nikita's new-in-ex= pressions RFC would allow for this (also using my short-functions RFC fo= r this example, although that's a nice-to-have): sealed class Maybe permits Some, None { public const None =3D new None(); static public function Some($x) =3D> new Some($x); public function value() =3D> throw new NotFoundException(); public function bind(callable $c) =3D> static::None; } final class None extends Maybe {} final class Some extends Maybe { private $val; private function __construct($x) { $this->val =3D $x; } public function value() =3D> $this->val; public function bind(callable $c) =3D> new static($c($this->val)); } Now if you have an instance of Maybe, you can be absolutely guaranteed t= hat it's either an instance of Some or of None. It's very similar to th= e guarantee you get for enumerations, that you will have one of a fixed = set of dev-defined values and don't need to worry about any other case. = You handle None, you handle Some, and now your function is a total func= tion over its Maybe parameter. There are assorted other cases along those lines. That gets you essentially the same functionality by a different route as= what the tagged unions RFC (https://wiki.php.net/rfc/tagged_unions) pro= poses: enum Maybe { case None { public function bind(callable $f) =3D> $this; } }; =20 case Some(private mixed $value) { public function bind(callable $f): Maybe =3D> $f($this->value); }; =20 public function value(): mixed =3D> $this instanceof None=20 ? throw new Exception() : $this->val; } Or to use another example from the tagged unions RFC: enum Distance { case Kilometers(public int $km); case Miles(public int $miles); } vs: sealed interface Distance permits Kilometers, Miles { ... } class Kilometers implements Distance { public function __construct(public int $km) {} } class Miles implements Distance { public function __construct(public int $miles) {} } In either case, a function can now operate on distance and know that it'= s dealing with a value in miles OR in kilometers, but it doesn't have to= worry about yards, furlongs, or light-years. Combined with a pattern-m= atching operator (which Ilija is working on here: https://wiki.php.net/r= fc/pattern-matching), it would make it possible to combine ADTs/sealed c= lasses with a match() statement and know that you've covered every possi= ble situation with a trivial amount of code. Enums, Sealed classes, and tagged unions all play in the same logical sp= ace, of allowing the developer to more precisely define their problem sp= ace and data model in a way that "makes invalid states unrepresentable",= ,and thus eliminates a large amount of error handling resulting in code = that is harder if not impossible to "get wrong." =20 I think it's clear that there's desire to have such capability, but the = specifics of how we get there are not as clear-cut. For instance, as cu= rrently envisioned tagged unions would extend enums, and as they're a de= dicated language construct we can build in read-only properties. Sealed= classes wouldn't be able to do that... however, if we also added asymme= tric visibility to properties or Nikita's proposed property accessors, a= class could itself force a property to be public-read-only. At that po= int, you would be able to implement the entire tagged-union RFC's functi= onality by combining sealed classes, read-only properties, and pattern m= atching. It would just have a less specific-to-the-use-case syntax, whi= ch could be good or bad depending on your point of view. I hope that clears up the problem space this RFC is working in. Whether= we want to achieve that functionality through enum-based tagged unions = or through sealed classes, new-in-expression, and read-only properties i= s an open question, and I'm not entirely sure yet which I favor. I can = see pros and cons to both approaches; I just know I really want at least= one of them, in 8.1 if at all possible. :-) --Larry Garfield