Re: [Concept] Extension methods
| From: | Alex Wells | Date: | Thu, 11 Aug 2022 09:03:11 +0000 |
| Subject: | Re: [Concept] Extension methods | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-118410@lists.php.net to get a copy of this message | ||
> On 11 Aug 2022, at 07:36, Paul Crovella <paul.crovella@gmail.com> wrote:
>
> This isn't impossible. There's nothing stopping an IDE from seeing
>
$collection-> and suggesting/completing that with
> map($collection, - they just don't right now. Offhand I
> don't see why this would be more difficult for them to implement than support for extension
> methods. As a bonus it'd work with existing functions.
What’s stopping them is that those functions aren’t methods. It’d be strange for
$collection->m to auto-complete to map($collection, . Even more strange
would be the case where you’re trying to auto-complete on a big multiline expression.
Besides, I believe working with existing functions is more of a problem than a bonus - because of
the naming. You’d have to clutter your code with array_map calls, even though it
could easily be shortened to map.
> On 11 Aug 2022, at 07:36, Paul Crovella <paul.crovella@gmail.com> wrote:
>
> In short every ugly example has a fine, easy-to-read way of writing it now in existing code. A
> useful readability comparison would pit the new feature against the best looking version of example
> code you can come up with rather than the worst. Without that you're not really demonstrating
> improvement.
That’s just a concept. I’d love to bring a lot more examples in an RFC if there’s more
positive than negative feedback. Again, I’m more looking for feedback than trying to convince
someone, but I’ll showcase a couple real comparisons for some context:
Compere these (copied from real world project/public composer packages):
```php
$className = Str::studly(implode('_', array_slice(explode('_',
$file->getName()), 4)));
// vs
$className =
$file->getName()->explode(‘_’)->slice(4)->implode(‘_’)->studly();
array_map(fn (string $item) => trim(mb_strtolower(strip_tags($item))),
trans('contacts::tenants.gender_types’));
// vs
trans('contacts::tenants.gender_types’)->map(fn (string $item) =>
$item->stripTags()->lower()->trim());
array_merge($oldResponse, array_map(fn (Response $v) => Arr::except($v->toArray(),
'message'), $newResponse))
// vs
$oldResponse->merge($newResponse->map(fn (Response $v) =>
$v->toArray()->except(‘message')))
```
All these changes lead to a lower cognitive load, because:
- you aren’t dealing with lots of nested parenthesis
- you aren’t trying to read expressions “inside-out”
- you aren’t messing with not-so-useful prefixes like Arr::, array,
str
You could argue the problem is that all of these are single-liners, so here are the same examples,
but multiline formatted:
```php
$className = Str::studly(
implode(
'_’,
array_slice(
explode(
'_’,
$file->getName()
),
4
)
)
);
// vs
$className = $file->getName()
->explode(‘_’)
->slice(4)
->implode(‘_’)
->studly();
array_map(
fn (string $item) => trim(
mb_strtolower(
strip_tags($item)
)
),
trans('contacts::tenants.gender_types’)
);
// vs
trans('contacts::tenants.gender_types’)->map(
fn (string $item) => $item
->stripTags()
->lower()
->trim()
);
array_merge(
$oldResponse,
array_map(
fn (Response $v) => Arr::except($v->toArray(), 'message'),
$newResponse
)
)
// vs
$oldResponse->merge(
$newResponse->map(
fn (Response $v) => $v
->toArray()
->except(‘message’)
)
)
```
I believe the difference is quite obvious.
Not an argument, but one more thing to consider: if current state of things in PHP was “good
enough”, why do library authors come up with those magic method trait solutions, and why we’re
seeing types like Collection being used instead of array and
Stringable (laravel) instead of string, when you could instead just
introduce a bunch of functions?
> On 11 Aug 2022, at 07:36, Paul Crovella <paul.crovella@gmail.com> wrote:
>
> An extension method would shift a single argument from inside parentheses to the left of the
> function name. It just moves it. I don't see any impact here.
It doesn’t move it, it removes it. You’re technically not passing that value as an argument
anymore anywhere in your code, your value becomes the implicit expression you’re working with.
That becomes even more obvious when multiple method calls are chained and don’t need to pass it
into every function call.
> On 11 Aug 2022, at 07:36, Paul Crovella <paul.crovella@gmail.com> wrote:
>
> This is essentially making -> the pipe operator with extra
> steps (extension/use extension) and
> less utility (not working on existing functions.)
Well, pipe operator is another option, but it’s got it’s downsides compared to extension
methods:
- it's less versatile: extension methods are required to specify a type they’re extending,
meaning they are methods, not functions. Hence, two different map method extensions can
be imported in a single file (given they’re for different types - say one for
Collection, the other for array), unlike regular functions. I believe
it’s common place to use both Collection::map and array::map in a single
file, but that wouldn’t be possible or would require aliasing
- it’s uglier: since it just uses functions, to avoid clashes between same method names, prefixes
would be required