Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:114147 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 9495 invoked from network); 25 Apr 2021 14:44:23 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 25 Apr 2021 14:44:23 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id A5CC71804DB for ; Sun, 25 Apr 2021 07:48:09 -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=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-Virus: No X-Envelope-From: Received: from mail-qv1-f41.google.com (mail-qv1-f41.google.com [209.85.219.41]) (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 ; Sun, 25 Apr 2021 07:48:09 -0700 (PDT) Received: by mail-qv1-f41.google.com with SMTP id x14so2764826qvr.5 for ; Sun, 25 Apr 2021 07:48:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=newclarity-net.20150623.gappssmtp.com; s=20150623; h=mime-version:subject:from:in-reply-to:date:cc :content-transfer-encoding:message-id:references:to; bh=VdjbEfTrMLvMkNzlaGlPRixMbTy/vgUY7YwqGajUrMU=; b=nfEK1WzNX5SpucK0qkEJi/E1j0x+dlLckL9Uv67BQ6kxSI8Ji++9uXG4mLt/vVpPZD 9zBG1niJUKg+bQ2FXiFKQZg6bBb51kaBLiSe5dkjaOnVZwT+HYe6VFaj6CQKUcjYYqOR X3e3qzk2+6EcWta6qeFkV/8lRZTAqqcIFjRn+GUHw5LGlmEClJGyLXbFs5pVG0ytUbbH m0a6H490t1Zb6Zvd7lwvErN+AdC3JxQJVWKY8Zp+FqMZzmuKtSqzc1sSSItEc85Ah5Ik YJR/dDVdh8JAEoJuE1KSviO2JBkErpFonx+tjF01fzO8K8kwmFClj8nWF49AvmNlilI8 jPcg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:subject:from:in-reply-to:date:cc :content-transfer-encoding:message-id:references:to; bh=VdjbEfTrMLvMkNzlaGlPRixMbTy/vgUY7YwqGajUrMU=; b=s2ZcA80p/09mR1hiZiSmd1H1yXATrQifiYU3xuYy9pzTkAJjXYByasnM0uCGR6PxDe gfkckPtQAtwwVQtCY9i7ixQK4VPvB0WZ55BKNlfnWy7GHdd5HUPIUt9OlFKd31uDZoKQ IlzFTs33bIuS1TojQLfSPb2oDScPKMnFD95ZR0MHLkb+qoCgD/JUAOEmh2Zsvxg/VOx7 EqCRbvXgKn4O7irHg0m7LLnSAePL8JlLRxEWR95OW812wqY1lJSPkDsPx4YEYyAYxxSK cRSdj6XMWD2AA7dkcQwjaXZO6bvgN6M4nsWYmF0N1lCRAYURu6wpPZaz9m2tj/YBKaut SkIg== X-Gm-Message-State: AOAM530Y0HV+hU3GD5seJEsm95i1lr4hNyfBN6lKrO1OJ/Isb0pIr+et JWPUvjbqF3ld5lddbBs9MJJAVy+JpQbALw== X-Google-Smtp-Source: ABdhPJyiA0mmlobwlgVWN71oiQLzu+kDDyEF4oHUv4sncsNJjwgi0iOi+xQWy++GbHSIgc3axIbxFA== X-Received: by 2002:ad4:4e44:: with SMTP id eb4mr5642688qvb.3.1619362084667; Sun, 25 Apr 2021 07:48:04 -0700 (PDT) Received: from [192.168.1.10] (c-24-98-254-8.hsd1.ga.comcast.net. [24.98.254.8]) by smtp.gmail.com with ESMTPSA id d3sm8408683qtm.56.2021.04.25.07.48.04 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sun, 25 Apr 2021 07:48:04 -0700 (PDT) Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.120.23.2.4\)) In-Reply-To: Date: Sun, 25 Apr 2021 10:48:03 -0400 Cc: php internals Content-Transfer-Encoding: quoted-printable Message-ID: <9B2BB3A2-0E47-436D-8002-D0662C481ADB@newclarity.net> References: <5b9f1500-615a-48f1-815f-1d48b327ef90@processus.org> <179049b1475.11134368b213512.254739612773841999@void.tn> To: Larry Garfield X-Mailer: Apple Mail (2.3608.120.23.2.4) Subject: Re: [PHP-DEV] [RFC][Draft] Sealed Classes From: mike@newclarity.net (Mike Schinkel) > On Apr 24, 2021, at 11:47 PM, Larry Garfield = wrote: >=20 > 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: >>>=20 >>>> 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? >>>>>=20 >>>>> This depends on which syntax is picked, both `for` and attribute = syntax >>>> will >>>>> be completely BC. >>>>=20 >>>> I'm not talking about BC, but the maintainability of the new = feature >>>> itself. For the shape example, you'd need to edit the original file >>>> for each new shape you add, which is detrimental for = maintainability >>>> and scalability. So what's a good use-case? >>>>=20 >>>=20 >>> The main use-case of sealed types is being able to declare total = functions >>> 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 >=20 > 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, = because for every possible pair of integers you pass to it there is a = logical return value. However, square root is not a total function over = integers because there are some integers you pass it for which there is = not representable return value. (Negative numbers, unless you get into = imaginary numbers which PHP doesn't support.) In those cases, you have = to throw an exception or return an error code or similar. >=20 > 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 have to deal with "user not found" error handling. = getUser() is not a total function. getUsers(array $criteria), however, = arguably is, because it's logical and reasonable to map all not-found = cases to an empty array/collection, which doesn't require any special = error handling. >=20 > In practice, I think all of the use cases for sealed classes are = ADT-esque. As I noted before, combining sealed classes with Nikita's = new-in-expressions RFC would allow for this (also using my = short-functions RFC for this example, although that's a nice-to-have): >=20 > sealed class Maybe permits Some, None { >=20 > public const None =3D new None(); >=20 > static public function Some($x) =3D> new Some($x); >=20 > public function value() =3D> throw new NotFoundException(); >=20 > public function bind(callable $c) =3D> static::None; > } >=20 > final class None extends Maybe {} >=20 > final class Some extends Maybe { > private $val; > private function __construct($x) { $this->val =3D $x; } >=20 > public function value() =3D> $this->val; >=20 > public function bind(callable $c) =3D> new static($c($this->val)); > } >=20 > Now if you have an instance of Maybe, you can be absolutely guaranteed = that it's either an instance of Some or of None. It's very similar to = the 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 function over its Maybe parameter. >=20 > There are assorted other cases along those lines. >=20 > That gets you essentially the same functionality by a different route = as what the tagged unions RFC (https://wiki.php.net/rfc/tagged_unions) = proposes: >=20 > 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; > } >=20 > Or to use another example from the tagged unions RFC: >=20 > enum Distance { > case Kilometers(public int $km); > case Miles(public int $miles); > } >=20 > vs: >=20 > sealed interface Distance permits Kilometers, Miles { ... } >=20 > class Kilometers implements Distance { > public function __construct(public int $km) {} > } >=20 > class Miles implements Distance { > public function __construct(public int $miles) {} > } >=20 > 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-matching operator (which Ilija is working on here: = https://wiki.php.net/rfc/pattern-matching), it would make it possible to = combine ADTs/sealed classes with a match() statement and know that = you've covered every possible situation with a trivial amount of code. >=20 > Enums, Sealed classes, and tagged unions all play in the same logical = space, of allowing the developer to more precisely define their problem = space 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." Nothing I am about to write is meant to question the value of total = functions, but can you speak to the ramifications of a developer using a = library with a total function that returns miles or kilometers in (say) = version 2.0, along with the assumed guarantees of said function, but = then in version 3.0 the developer adds furlongs as a unit of measure? Yes going from 2.0 to 3.0 is a breaking change per semver, but it = doesn't feel like it had to be had the application developer not made = the total function assumption. -Mike >=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 currently envisioned tagged unions would extend enums, and as they're = a dedicated language construct we can build in read-only properties. = Sealed classes wouldn't be able to do that... however, if we also added = asymmetric visibility to properties or Nikita's proposed property = accessors, a class could itself force a property to be public-read-only. = At that point, you would be able to implement the entire tagged-union = RFC's functionality by combining sealed classes, read-only properties, = and pattern matching. It would just have a less = specific-to-the-use-case syntax, which could be good or bad depending on = your point of view. >=20 > 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 is 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. :-) >=20 > --Larry Garfield >=20 > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >=20