> 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