> On Apr 24, 2021, at 11:47 PM, Larry Garfield <la...@garfieldtech.com> wrote:
>
> 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."
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
>
> 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
>
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php