On Sat, Sep 7, 2024, at 15:28, Larry Garfield wrote:
> On Fri, Sep 6, 2024, at 7:46 PM, Davey Shafik wrote:
> 
> > My main struggle with this is readability. As much as I want custom 
> > types (and type aliases is a good chunk of the way there), the main 
> > issue I have is understanding what the valid inputs are:
> >
> > function foo(Status $string): void { }
> >
> > How do I know that Status is a) not a class, b) that I can fulfill the 
> > requirement with a string, and/or maybe any object with __toString(), 
> > or maybe it’s ints? Or objects or enums?
> >
> > Even with file-local aliases (which I would definitely prefer to avoid) 
> > we will most likely rely on developer tooling (e.g. IDEs and static 
> > analyzers) to inform the developer what the right input types are.
> >
> > I would very much prefer to either go all in with an Enum-like (which 
> > means that we can hang methods on to the value) or we need to 
> > distinguish between type hints for class-likes and type hints for 
> > not-class-likes (*Bar anyone?).
> >
> > Expanding on type-class-likes: within the type methods, $this->value 
> > would refer to the original value, any operators would follow the 
> > _same_ rules as either the original values type (e.g. $int = 4; $string 
> >  = “foo”; $int . $string == “4foo", or call __toString() in all the 
> > normal places for strings if defined).
> >
> >
> > type Stringable: string|int {
> >      public function __toString(): string
> >      {
> >           return (string) $this->value; // original value
> >      }
> >
> >      // Add Stringable methods here
> > }.
> 
> Methods on typedefs was the sort of "other stuff classes do" that I was 
> trying to avoid getting into right now. :-)  Mainly because I can totally see 
> how it's tempting, but also have no idea what it would mean from a 
> type-theoretic perspective.  It would only make sense if we're talking type 
> DEFs, not type ALIASes.  I'm not against that, and it could be fun to try and 
> think through the type theoretical implications, but I don't think that's 
> what Rob was going for so I didn't want to take that side quest just yet.  
> (Though if he's OK with it, I'm OK with it.)

To be fair, I should probably mention that I've already explored it some (which 
I alluded to in another thread a couple of weeks ago). 😉 So... I guess it is 
'on-topic' for no other reason than it is interesting.

Here are my notes thus far:

__Sugary Generics__

Since we could attach behaviors (at least at the engine level) we could use 
this to implement generics. Imagine we have a Box<T> and want to instantiate a 
Box<int|float>. To do this, when we define a Box<T>, we actually define an 
alias internally.

This is what the definition of our generic box class in PHP might look like:

class Box<T> { public T $var; }

It would get compiled to something like this (though it would probably be 
invalid php, it perhaps illustrates my meaning):

class Box {
  public BoxT $var;
  public function __construct(private alias BoxT) {}
}

Then, to instantiate a Box<int|float >, the engine compiles the constructor, 
passing the types as arguments (still probably not ever valid php) and stealing 
a bit from python's "self":

$box = new Box<int|float>; // compiles to $box = new Box(alias int|float);

The beauty of this is that it automatically becomes an error if you forget the 
type argument (and if we made it the last argument, would allow setting default 
values for BC reasons like Collection<T = mixed>)

Even constraints on T could be expressed similarly:

class Box<T: int|float> { public T $var; }

which may get compiled into this not ever valid php:

class Box {
  public BoxT $var;
  private alias BoxTConstraint => int|float;
  public function __construct(private alias BoxT) {
    if (BoxT is not a BoxTConstraint) throw new RunTimeException();
}

Basically, it just needs to check that the BoxT alias is of the right type 
during construction, essentially making generics just a sugary layer over 
aliases.

__Ambitious Type System Replacement__

I've also explored it in the case of types, in general, by replacing the entire 
type system with this way of representing types (where a special class 
represents a real type of value and its behavior). This would allow for 
defining casting rules, operators, passing types as values (for pattern 
matching), etc on types themselves. I have no idea what that would look like 
"at scale", but it is interesting to think about because primitive types would 
have the same way of working as classes and everything else. It would also 
separate types from their implementation—whether we want to expose this to 
user-land is a different story. 

I suspect this could be a 'technical-only' change and not affect user-land at 
all. zvals would probably get a lot simpler though... 

I generally stop myself from thinking too much about it, because while 
interesting, it is a TON of work. Not that I'm afraid of doing that work, I 
just don't want to do it by myself. So, I'm cautiously optimistic as a >=9.0 
type thing and finding the right people/support. I have no idea how to do that, 
but I can at least try.

__Constraints__

Another exploration is that it would potentially allow for setting some 
constraints on raw values:

alias EmailAddress => string {
  if (!is_valid_email($value)) throw new RuntimeException('not a valid email 
address');
}

However, I think there is a better solution for that 
(classes/structs/records/etc)... except for

__Typed Literals__

This could easily allow typed literals for value types. After all, a typed 
literal can be expressed as an alias over a type with a constraint.

__The Hammer and Screw Problem__

There are probably other use-cases by making the type-system more 'class-like', 
but I will say that PHP's class-system is quite robust—if not as robust as its 
arrays—and should probably be relied on more. That being said, I've been 
working through this for a bit now, and it seems that I might be wielding this 
as a hammer and seeing nails everywhere, even when they are screws. So, while 
interesting, in many cases, it probably isn't the best way to do things.

— Rob

Reply via email to