> On Sep 6, 2024, at 16:40, Rob Landers <rob@bottled.codes> wrote: > > > > On Sat, Sep 7, 2024, at 01:34, Larry Garfield wrote: >> On Fri, Sep 6, 2024, at 6:27 PM, Rob Landers wrote: >> >> >> I suspect there's also other edge case bits to worry about, particularly >> >> if trying to combine a complex alias with a complex type, which could >> >> lead to violating the DNF rule. For example: >> > >> > Oh, DNF is the bane of my existence with this RFC—I don't want to mess >> > this up. I'll see you at the end of the example, though. >> > >> >> >> >> typealias Foo: (Bar&Baz)|Beep; >> >> >> >> use (Bar&Baz)|Beep as Foo; >> >> >> >> function narf(Foo&Stringable $s) {} >> >> >> >> With the compile time approach, that would expand to >> >> `(Bar&Baz)|Beep&Stringable`, which is not a valid type def. >> > >> > I can see how you arrived at this, but I think you may have missed a >> > step, since the entirety of Foo will be &'d with Stringable. >> > >> > Foo = (Bar & Baz) | Beep >> > >> > want: (Foo) & Stringable >> > >> > expand Foo: ((Bar & Baz) | Beep) & Stringable >> > >> > Which can be reduced to the following in proper DNF (at least, it >> > compiles—https://3v4l.org/0bMlP): >> > >> > (Beep & Stringable) | (Bar & Baz & Stringable) >> > >> > It's probably a good idea to update the RFC explaining how expansion works. >> >> Woof. We're not "fixingup" anyone's DNF elsewhere. I cannot speak for >> everyone, but I'd be perfectly fine not doing any magic fixing for now, and >> then debating separately if we should do it more generally. Just doing it >> for aliases doesn't seem like the best plan. >> >> --Larry Garfield >> > > Oh, we're definitely not "fixingup" the expression to DNF... more like > spending some time in the RFC showing how the expansion is the same execution > as with a DNF expression to prove that it is a valid type expression. > > — Rob
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 }. So, with that in mind… I’d also like to open up the ability for Enums to be fulfilled by the backed value, that is: function foo(Bar $bar): void { } Where Bar is: enum Bar: string { case BAZ = 'baz'; case BAT = ‘bat'; } And you can call `foo()` like: foo(‘baz’) and Bar::BAZ will be passed in. I realize I’m opening a barn down here, but I just don’t see file-local type aliases as that useful, and while I like the functionality of type-class-likes, I think they would add more non-class behavior (in addition to enums) for things that look like classes if we don’t add some sort of identifier. I’d much rather that we add backed-value to enum casting, and at least make that more consistent with this functionality if we’re going to conflate the syntax. - Davey