On Sat, Apr 24, 2021, at 2:55 PM, Olle Härstedt wrote: > 2021-04-24 21:51 GMT+02:00, Marco Pivetta <ocram...@gmail.com>: > > On Sat, Apr 24, 2021, 21:44 Olle Härstedt <olleharst...@gmail.com> wrote: > > > >> 2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati <azj...@void.tn>: > >> >> 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 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? > >> > > > > The main use-case of sealed types is being able to declare total functions > > around them. > > 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. > > 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, 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. 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. 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): sealed class Maybe permits Some, None { public const None = new None(); static public function Some($x) => new Some($x); public function value() => throw new NotFoundException(); public function bind(callable $c) => static::None; } final class None extends Maybe {} final class Some extends Maybe { private $val; private function __construct($x) { $this->val = $x; } public function value() => $this->val; public function bind(callable $c) => new static($c($this->val)); } 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. 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) proposes: enum Maybe { case None { public function bind(callable $f) => $this; } }; case Some(private mixed $value) { public function bind(callable $f): Maybe => $f($this->value); }; public function value(): mixed => $this instanceof None ? 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-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. 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." 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. 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. :-) --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php