Hi internals,
I'd like to start the discussion on a new RFC:
https://wiki.php.net/rfc/array_partition
It proposes a function that splits an array into the elements for which
a callback returns a truthy value and those for which it does not, in a
single pass:
function array_partition(array $array, callable $callback): array {}
[$adults, $minors] = array_partition($users, fn($u) => $u->age >= 18);
Keys are preserved, and the callback receives the value and the key,
following array_find(). The result is always a list of two arrays, with
the matching elements first, as in Ruby, Kotlin, Rust, Haskell and
Laravel's Collection::partition().
The RFC also covers the points raised in the karma request thread:
why it is a two-way split rather than a general grouping function, why
the result is not keyed by true/false, and the $callback parameter
name. It also discusses the previous userland uses of the name
array_partition() with an "N chunks" meaning.
The implementation is at https://github.com/php/php-src/pull/24568.
Thanks to Ilija for granting the karma, and to everyone who commented
on the earlier thread. I look forward to your feedback.
Ayoub
I wouldn't be disappointed to see array_partition() added to the language,
but the longer I think about it, a well-designed array_group() function
could provide the equivalent behavior as well as afford other than just 2
static groups.
It seems like the only relevant difference is the fact that
array_partition() always returns an indexed array of two arrays without
needing to push 2 empty arrays for padding. Theoretically, array_group()
might not return any subarrays when there are no qualifying items to
retain. Does array_group() need an optional 3rd parameter which declares
an empty array for each nominated grouping key which can be nominated in a
particular order? Food for thought.
Instead of potentially creating array_partition() and eventually
array_group(), I feel any benefits of array_partition() should be baked
into an array_group() function for greater utility in a unified function.
mickmackusa
Hi mickmackusa,
Instead of potentially creating array_partition() and eventually
array_group(), I feel any benefits of array_partition() should be baked
into an array_group() function for greater utility in a unified function.
Thanks for thinking it through. Seeded keys would indeed solve the
missing-bucket problem, and with string labels the result reads well:
['adults' => $adults, 'minors' => $minors] = array_group(
$users,
fn($u) => $u->age >= 18 ? 'adults' : 'minors',
['adults', 'minors'],
);
I still think the two-way split deserves its own function, for three
reasons:
-
Destructuring is by key, not by order. With a boolean callback, the
keys become 1 and 0 whatever order they are seeded in, so
[$adults, $minors] = array_group($users, $predicate, [true, false])
puts the minors in $adults. You have to switch to labels, as above. -
With labels, the callback, the seed list and the destructuring keys
have to agree. A typo ('adult' vs 'adults') silently creates a third
group and leaves one variable empty. array_partition() only needs a
predicate, and its result shape is fixed. -
PHP already pairs general functions with simpler, specialised ones
for common cases:array_filter()witharray_find()andarray_any().
A two-way split is common enough to warrant the same treatment.
It doesn't rule out a future array_group(), which raises its own
design questions (return shape, unknown keys, key preservation).
I'll add the seeded-keys alternative to the RFC's "Why not a general
grouping function?" section.
Thanks,
Ayoub
I wouldn't be disappointed to see array_partition() added to the language, but the longer I think about it, a well-designed
array_group()function could provide the equivalent behavior as well as afford other than just 2 static groups.It seems like the only relevant difference is the fact that array_partition() always returns an indexed array of two arrays without needing to push 2 empty arrays for padding. Theoretically, array_group() might not return any subarrays when there are no qualifying items to retain. Does array_group() need an optional 3rd parameter which declares an empty array for each nominated grouping key which can be nominated in a particular order? Food for thought.
Instead of potentially creating array_partition() and eventually array_group(), I feel any benefits of array_partition() should be baked into an array_group() function for greater utility in a unified function.
mickmackusa
Hi
I'd like to start the discussion on a new RFC:
Thank you for your RFC. Incidentally a co-worker has hand-written an
array_partition() just last week (using two array_filter() calls),
so clearly there is some appetite for it.
I've just read the RFC and noticed a small rendering issue in the
“Semantics in detail” section. For the “An empty input” bullet point it
seems you didn't properly escape the code and it was interpreted as Wiki
markup.
For the arity example, you could add that is_string(?) with PFA’s ?
placeholder should work, since it behaves like a userland function.
For the “why not a general grouping function” section, I would like to
point towards the declined
https://wiki.php.net/rfc/destructuring_coalesce RFC, which is somewhat
related to the “The buckets are only created when at least one element
falls into them” argument.
For the “Backward incompatible changes” section, I'd like to note that
even for users defining array_partition with a function_exists()
check nothing guarantees that their custom implementation behaves the
same, so they would need to adjust their code either way. This is
acknowledged in the second paragraph, but the initial paragraph is
somewhat misleading. In any case, adding new symbols is not considered a
breaking change as per policy and the arguments for the proposed name
are strong, so this is not a hard blocker (but must be mentioned
nevertheless, as you did).
Perhaps it would also make sense to extend the stub in the proposal
section to a full “userland polyfill” (using the foreach loop), similar
to what the array_find() RFC did. This can help make the behavior
clear in a way that pure prose can't.
Other than these small polishing nits, I don't have anything to remark
about the RFC other than: It's a well-written, well-researched and
well-argued RFC and I'm in favor of the proposal.
Best regards
Tim Düsterhus
Hi Tim,
Thank you for the careful read, and for the kind words.
I've updated the RFC:
- Fixed the "[[], []]" rendering in "Semantics in detail".
- Added is_string(?) to the arity example. I verified that it works
with the implementation. - Linked the declined Destructuring Coalesce RFC from the
missing-buckets argument and from "Related RFCs". - Rewrote the first paragraph of "Backward Incompatible Changes": with
or without afunction_exists()guard, an existing userland
array_partition() needs to be checked against the native semantics. - Replaced the stub in the proposal with a userland implementation
using foreach. While checking it against the native function, I found
one difference: the native function keeps by-reference elements as
references, likearray_filter(), and the foreach version does not.
The RFC mentions it right below the code.
Best regards,
Ayoub
Hi
I'd like to start the discussion on a new RFC:
Thank you for your RFC. Incidentally a co-worker has hand-written an
array_partition()just last week (using twoarray_filter()calls),
so clearly there is some appetite for it.I've just read the RFC and noticed a small rendering issue in the
“Semantics in detail” section. For the “An empty input” bullet point it
seems you didn't properly escape the code and it was interpreted as Wiki
markup.For the arity example, you could add that
is_string(?)with PFA’s?
placeholder should work, since it behaves like a userland function.For the “why not a general grouping function” section, I would like to
point towards the declined
https://wiki.php.net/rfc/destructuring_coalesce RFC, which is somewhat
related to the “The buckets are only created when at least one element
falls into them” argument.For the “Backward incompatible changes” section, I'd like to note that
even for users definingarray_partitionwith afunction_exists()
check nothing guarantees that their custom implementation behaves the
same, so they would need to adjust their code either way. This is
acknowledged in the second paragraph, but the initial paragraph is
somewhat misleading. In any case, adding new symbols is not considered a
breaking change as per policy and the arguments for the proposed name
are strong, so this is not a hard blocker (but must be mentioned
nevertheless, as you did).Perhaps it would also make sense to extend the stub in the proposal
section to a full “userland polyfill” (using the foreach loop), similar
to what thearray_find()RFC did. This can help make the behavior
clear in a way that pure prose can't.Other than these small polishing nits, I don't have anything to remark
about the RFC other than: It's a well-written, well-researched and
well-argued RFC and I'm in favor of the proposal.Best regards
Tim Düsterhus
Hi
Thank you. One note regarding the mailing list: Please do not “top
post”. Put your reply below the quoted original parts of the email you
are replying to and cut the quote to the relevant bits, just like I'm
doing here:
- Fixed the "[[], []]" rendering in "Semantics in detail".
One hint for the Wiki: Using <php> around code will perform the proper
escaping, and will also enable syntax highlighting.
- Replaced the stub in the proposal with a userland implementation
using foreach. While checking it against the native function, I found
one difference: the native function keeps by-reference elements as
references, likearray_filter(), and the foreach version does not.
The RFC mentions it right below the code.
Good that I asked for the example implementation - and that you spelled
out the difference, because I did not realize the significance of the
“By-reference elements of the input are handled in the same way as in
array_filter()” in the “Semantics in detail” section - and there is no
corresponding example either. Can you please add another “edge case”
example that clearly shows the reference handling behavior to make sure
every reader is aware of the full implications.
I'm also not sure, I like the fact that array_partition() has behavior
that cannot be replicated in userland. I can see the “follow
array_filter()” argument for consistency, but at the same time
array_find() is dropping references (I believe) and I also feel that
“filtering an array” (i.e. just removing entries) is semantically
somewhat different than “creating new arrays with the original
elements”. There are probably good arguments for both positions and I'd
like to hear some more opinions from others on this matter.
Best regards
Tim Düsterhus
Hi Tim,
Please do not "top post".
Sorry about that, noted.
One hint for the Wiki: Using
<php>around code will perform the proper
escaping, and will also enable syntax highlighting.
Thanks, I've switched that bullet to <php>.
Can you please add another "edge case"
example that clearly shows the reference handling behavior to make sure
every reader is aware of the full implications.
Done, in a new "By-reference elements" subsection. It shows that
writing through the result changes the input (and the other way
round), and that a reference which is no longer shared is copied as a
plain value. The outputs in the examples come from running them against
the implementation.
I'm also not sure, I like the fact that
array_partition()has behavior
that cannot be replicated in userland. I can see the "follow
array_filter()" argument for consistency, but at the same time
array_find()is dropping references (I believe)
You're right about array_find(). I checked the other functions in
ext/standard to see where array_partition() would fit:
keep references: `array_filter()`, `array_diff_key()`, `array_slice()`,
`array_values()`, `array_chunk()`
drop references: `array_find()`, `array_map()`
So the functions that return part of their input as an array all keep
references. array_find() returns a single value, which can't hold a
reference anyway. I think the strongest argument for keeping them is
that replacing the current array_filter() + array_diff_key() idiom with
array_partition() then doesn't change behaviour.
That said, your point about userland replicability is fair, and
dropping references would be a one-line change. I've added it to "Open
Issues" so it gets settled before a vote, and I'm happy to go with
whatever the list prefers.
Best regards,
Ayoub