feat: flat_map と group_map を追加する - #86
Open
Javakky wants to merge 2 commits into
Open
Conversation
Javakky
force-pushed
the
javakky/flat-map-group-map
branch
from
July 27, 2026 07:17
688b6b6 to
602bad8
Compare
Javakky
added a commit
that referenced
this pull request
Jul 27, 2026
出力キーを指す名前が G (group_by) と R of array-key (map_keys) に分裂していた。 さらに R は reduce と chunk_by では「集約結果」「比較用の中間値」の意味で使われていて、 bound の有無で 2 つの 役割を持っていた。 関数型言語の R は変換関数が返す「値」を指す (Kotlin map(transform: (T) -> R): List<R> / Java Function<T, R> / Scala map[B])。 map_keys が R of array-key を使っていたのは「callback の 戻り値」の意味で R を選び、 それが結果的にキーになるという用法で、 キー側の名前として使っていた わけではない。 R は値側に限定し、 出力キーには I を与える。 K 入力のキー型 V 入力の値型 I 出力のキー型 E 出力の値型 (配列の要素) R 配列でない戻り値 (reduce の集約結果、 chunk_by の比較値) reduce.php と chunk_by.php の R は触らない。 今やる理由: count_by / associate / group_map / flat_map はまだ master に無く open PR (#81 / #86) 側にある。 それらがマージされる前に規約を確定させるのが最も安い (後からだと 11 本すべてに追随 コミットが必要になる)。 I という文字自体に PHP の用例は無い。 出力キーに名前を与えている実例は 3 つで、 C# LINQ の ToDictionary<TSource, TKey, TElement> は TKey を selector の戻り値として再定義するが phollection は 入力キーに K を使うため衝突する。 Haskell の mapKeys :: (k1 -> k2) -> Map k1 a -> Map k2 a は 入力キーの改名が 33 箇所に及ぶ。 Ramda の groupBy<T, K extends string> は出力キー専用の型パラメータを 立てていて、 独立した名前を与えるという発想はこれと同じ。 Swift / Rust / Python / lodash / fp-ts は キーを変換する標準 API が無く概念自体が存在しない。 Refs #76
Javakky
added a commit
that referenced
this pull request
Jul 27, 2026
出力キーを指す名前が G (group_by) と R of array-key (map_keys) に分裂していた。 さらに R は reduce と chunk_by では「集約結果」「比較用の中間値」の意味で使われていて、 bound の有無で 2 つの 役割を持っていた。 関数型言語の R は変換関数が返す「値」を指す (Kotlin map(transform: (T) -> R): List<R> / Java Function<T, R> / Scala map[B])。 map_keys が R of array-key を使っていたのは「callback の 戻り値」の意味で R を選び、 それが結果的にキーになるという用法で、 キー側の名前として使っていた わけではない。 R は値側に限定し、 出力キーには I を与える。 K 入力のキー型 V 入力の値型 I 出力のキー型 E 出力の値型 (配列の要素) R 配列でない戻り値 (reduce の集約結果、 chunk_by の比較値) reduce.php と chunk_by.php の R は触らない。 今やる理由: count_by / associate / group_map / flat_map はまだ master に無く open PR (#81 / #86) 側にある。 それらがマージされる前に規約を確定させるのが最も安い (後からだと 11 本すべてに追随 コミットが必要になる)。 I という文字自体に PHP の用例は無い。 出力キーに名前を与えている実例は 3 つで、 C# LINQ の ToDictionary<TSource, TKey, TElement> は TKey を selector の戻り値として再定義するが phollection は 入力キーに K を使うため衝突する。 Haskell の mapKeys :: (k1 -> k2) -> Map k1 a -> Map k2 a は 入力キーの改名が 33 箇所に及ぶ。 Ramda の groupBy<T, K extends string> は出力キー専用の型パラメータを 立てていて、 独立した名前を与えるという発想はこれと同じ。 Swift / Rust / Python / lodash / fp-ts は キーを変換する標準 API が無く概念自体が存在しない。 Refs #76
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要(What / Why)
callback の戻り配列を 1 段だけ平坦化する
flat_mapと、group_byしつつ各要素を変換するgroup_mapを追加します。変更点
src/flat_map.php— LIST は連番へ正規化、ASSOC は callback 戻りのキーを採用 (衝突は後勝ち)src/group_map.php—group_byとキーの扱いを揃え、各要素を$transformで変換tests/FlatMapTest.php(12 ケース) /tests/GroupMapTest.php(8 ケース)composer.jsonのautoload.filesに 2 件追加動作確認
flat_map([1, 2], fn ($v) => [$v, $v * 10])→[1, 10, 2, 20]group_map(['alice' => 20, 'bob' => 17], fn ($age) => $age >= 20 ? 'adult' : 'minor', fn ($age) => $age * 12)→['adult' => ['alice' => 240], 'minor' => ['bob' => 204]]vendor/bin/php-cs-fixer fix --dry-run --diff/vendor/bin/phpstan analyse -c phpstan.neon/vendor/bin/phpunit testsOK (117 tests, 122 assertions)/ PHPStan level 10[OK] No errors/ CS FixerFixed 0 of 33 files補足(任意)
flat_mapの平坦化は 1 段だけ戻り配列の中の配列は展開しません (phpdoc に明記、テストで固定)。
MODE_ASSOCは外側キーを捨てる入力側のキーではなく callback 戻り配列のキーを採用し、衝突は後勝ちです。テストでは値が後勝ちになることに加えてキーの位置が初出のまま残ることまで固定しています (PHP の上書きはキー位置を移動しないため)。
list 入力に
MODE_ASSOCを明示すると callback 戻りの内側キーが全部 0 になって 1 件に潰れます。仕様からは正しいものの驚きやすい組み合わせなので、これもテストで固定しました。@template R of array-keyが解決されることを確認済みcallback が list を返す呼び出しで
Rが推論されずarray<never, E>に落ちる懸念がありましたが、\PHPStan\dumpType()で 7 パターン確認したところ 1 件も出ませんでした (list 戻りのケースはarray<0, 1|2>と解決され、実行時結果[0 => 2]を正しく含みます)。group_mapはModeを取りませんgroup_byと同じく入力形状でキーの扱いを決めます (list 入力なら各グループは list、assoc 入力なら元のキーを保持)。Closes #57
Closes #20
🤖 Generated with Claude Code
追記:
group_mapにModeを追加しました新規関数には
Modeを持たせる方針に合わせました。当初は「group_byが取らないから」という理由で省いていましたが、それは既存関数の制約であって新規関数が従う理由になりません。MODE_LIST: 各グループが完全な list (入力が assoc でもキーを捨てる)MODE_ASSOC: 各グループが元のキーを維持 (list 入力でも元の整数添字を維持し、添字を詰めない)MODE_AUTO:array_is_list()判定でgroup_byと同じ挙動 = 既存テストはすべてそのまま通ります実装は
array_is_list($input)の直接呼び出しをやめてMode::check_mode($mode, $input)に置き換え、@phpstan-returnを 4 分岐の条件型にしました。OK (126 tests, 133 assertions)/ PHPStan level 10[OK] No errors。Note
group_by自体はModeを持たないままなので、group_map(..., MODE_LIST)に相当する挙動がgroup_byにはありません。既存関数への追随は別途相談させてください。