> On Sep 6, 2024, at 16:40, Rob Landers <[email protected]> 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