> 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

Reply via email to