2026年9月17日(木)掲載 4,418本日 27
HN77135

なぜ多くのエンジニアは「reduce」を避けたがるのか?可読性を高めるための考察

Anecdotally, programmers dislike "reduce"

vinhnx3日前

議論

11
0vinhnxスレ主773日前

個人的な観測ですが、多くのプログラマーが「reduce」メソッドを避ける傾向にあるようです。なぜ関数型プログラミングの強力なツールであるはずのreduceが敬遠されるのか、その背景にはコードの可読性やデバッグのしやすさに対する強いこだわりがあるのではないでしょうか。

1el_oni3日前

仕事でreduceを使ったのは数えるほどだけど、使うたびに「おっ」という反応をされるんだよね。でも、SparkのDataFrameをまとめてunionするなら、リストのDataFrameを reduce(DataFrame.union, list_of_dfs) で処理する方が、わざわざ先頭を取り出してループで回す書き方よりずっとスマートだと思う。functoolsからimportしなきゃいけなくなったせいもあってか、みんなちょっと構えすぎな気がするよ。

2rspeele2日前

mapやfilterはたいてい引数が1つで、もし2つあっても2番目は0始まりのインデックスって相場が決まってる。MicrosoftがSelectやWhereなんて呼んでいても、ほとんどの言語で見た目は同じだしね。ところがreduceにはアキュムレータと2引数の関数が必要なんだけど、言語によって初期値の置き場所やコールバックの引数順序がまちまちで統一感がない。reduce(初期値, callback(acc, elem)) なのか、逆なのか、あるいはcallback(elem, acc)なのか…。覚えるのが大変だよ。それに、初期アキュムレータを取らないタイプのreduceを用意してる言語もあって、空のコレクションに当たると即死する地雷になりかねないし。極めつけは、無名型をサポートしてない、あるいは無名レコードの書き換えが面倒な言語だと、アキュムレータの扱いがすごく厄介になること。ほとんどの言語がそうなんだけどね。

3s-zeng2日前

関数型プログラミングの世界でも、命令型コードにおけるwhileループが少し行儀の悪いコードとされるのと同様に、fold(reduce)もコードの不吉な臭い(code smell)になり得るという議論はあるよ。それぞれ使いどころはあるんだけど、これらは低レイヤーの反復プリミティブだから、もっと抽象度の高いもの(命令型ならforループやイテレータ、関数型ならモノイド的なreduceやモナド的な走査、再帰スキームなど)を使ったほうが良い場合が多い。whileループでforループを実装できるからといってそうしないのと同じで、関数型の走査でも同じことが言える。ただ、PythonやJavaみたいな言語だと、より強力な関数型走査の手段が用意されていないから、whileループしかない言語で作業しているような窮屈さを感じることになるんだ。

4snackbroken2日前

mapとfilterは、個々の要素を独立して推論できるから使いやすいんだよね。一方、reduce(fold)は中間結果を含めて全体を把握しなきゃいけない。それに、関連する型の「ゼロ値」をわざわざ用意しなきゃいけないのも、難しくはないにせよ、精神的なオーバーヘッドが少し増える要因だと思う。

5chubot約13時間前

パフォーマンスの話でいうと、Python 3でreduceが組み込みからfunctools.reduce()へ「追放」された時のことはよく覚えてるよ。2006年か2007年頃、Guido van Rossumが自作のGoogle社内コードレビューツールでWebページの表示に30秒以上かかる原因を調べていたんだ。数千人のエンジニアが使っていたツールだから、実質的な本番障害だよね。結局、reduce()で書かれた行折り返しアルゴリズムが原因だと判明したんだけど、これは依存ライブラリから紛れ込んだものだったみたい。reduce()って基本的には s1 + s2, s1 + s2 + s3... みたいに処理するから、文字列操作だと計算量がO(n^2)になってしまう。5000行以上の差分を表示するような場面でこれが顕著になったんだ(最近のGithubでも同じような問題が起きるよね)。Pythonでは当時すでに+=が最適化されていたはずだし、リストにappendして最後にjoinするのが定石だったけど、reduce()を使うと強制的に非効率な実装になってしまう。これはPython 3でも同じだ。結局Guidoはreduce()のパフォーマンス問題のデバッグに時間を取られた末に、ユーザーが地雷を踏まないように言語から遠ざける判断を下したんだ。当時彼と同じオフィスにいたからこの経緯を覚えているよ。ちなみにreduce自体は90年代に他の関数型イディオムと一緒に誰かがコントリビュートしたもの。Guido本人の好みは命令型スタイルで、後から少し後悔していたようだね。

6franey約13時間前

少なくともTypeScriptだと型定義が少し面倒だし、reduce関数の引数の順番(アキュムレータ、現在の要素)をよく忘れちゃう。自分だけかもしれないけど、コールバックの第1引数がアキュムレータなのに、reduce関数全体で見るとアキュムレータは第2引数になるから混乱しやすいんだよね。.filter()だと第1引数が現在の要素だし、そっちのほうが直感的でよく使うな。

7adverbly約10時間前

できるだけmapやfilterで置き換えてreduceの使用を減らそうとしている身からすると、reduceへの逃げはwhileループやforループへの逃げと同じ感覚かな。できるなら避けたい。reduceの問題は「何でもできてしまう」ことで、パッと読み返した時に何をしているのかが分かりにくいことだよね。

8catapart約10時間前

個人的には名前[0]がしっくりこないんだ。mapは別の配列からマッピングされた配列を返し、filterはフィルタリングされた配列を返す。どちらも結果は常に配列だ。でもreduceは、結果が入力配列の縮小版であるとは限らない。JS/TSみたいに、配列の要素と同じ型を返す必要すらない言語だと、整数の配列をキャンセル・トークンやステート・オブジェクトなどに「還元」することすらできてしまう。効率的なやり方ではないかもしれないけれど、コードは指示書のように読めるほうが好きだ。forループでできないことはreduceにもないし、アキュムレータを使ったforループの方がreduceより明確に読みやすい。mapなら何が起きるかすぐ分かるけど、reduceだと、確信があっても中身を確認しなきゃいけない。reduceが常に入力配列の要素を返してくれるなら使うんだけど、何でも返せてしまうならforループのシンプルさの方が好みかな。[0] もっと良い名前のアイデアはないんだけど、全体を一言で表すのは難しいよね。「dispatch」なら関数を各要素に適用するイメージだけど、戻り値の蓄積という概念が隠れてしまう。「transform」も正確だけど具体的じゃないし…。とにかく有用な関数であることは否定しないけど、理解しやすくデバッグしやすくするのは難しいね。

9antfarm約9時間前

クロージャとreduceを完全に理解したのは、2008年に最初のRailsの仕事でRubyを学んだ時だったな。1995年の大学のCS授業で再帰やポインタを理解した時と同じくらいの画期的な瞬間だった。当時は本で読んでも理解できなかったことが、ようやく分かったんだ。2024年にはSwiftでAdvent of Codeを解いたんだけど、ミュータブルな状態やループを使わずに、reduceをかなり多用してみたよ。

10very-old-sw約8時間前

CUDA Thrustのような並列関数型プログラミング言語において、バリア付きのreduceは不可欠だし、驚くほど強力だよ。