Need help?
<- Back

Comments (161)

  • rspeele
    Map and filter usually have only one arg and if they have 2, the 2nd is almost always a 0-based index. They look identical in most languages, even when Microsoft chooses to call them Select and Where.Reduce has an accumulator and a 2-arg function and languages are not very consistent amongst each other as to whether it's reduce(initial_acc, callback(acc, elem)) or reduce(callback(acc, elem), initial_acc) or reduce(callback(elem, acc), initial_acc) or what.Hard to remember. Also some languages have a version of reduce that doesn't take an initial accumulator at all, which is just a footgun waiting for you to hit an empty collection. Also ALSO, the accumulator can easily become awkward in languages that don't support anonymous types or don't support easy mutation of an anonymous type record. Which is most of them!
  • chubot
    Related to the point about worse performance, I'm pretty sure I was there when reduce was "banished" from Python 3 -- demoted to functools.reduce(), instead of the builtin reduce() in Python 2The story is that sometime in 2006 or 2007, Guido van Rossum was debugging why a web page in Google's internal code review tool (which he wrote) was taking 30+ seconds to render.This is basically a "production" incident, since thousands of Google engineers relied on the tool. Requests like this were probably tying up threads and exhausting thread pools, perhapsEventually it was tracked down to a line wrapping algorithm written with reduce(). I don't think he wrote it -- it may have come in through a dependency. As many know, reduce() is basically: s1 + s2 s1 + s2 + s3 s1 + s2 + s3 + s4 ... And that's O(n^2) when s_i are strings. And I think it showed up if you viewed a 5000+ line diff, or a 5000+ line file. (Newer programs like Github also suffer here)I believe, in Python at that time, += was already optimized to avoid this (just like essentially all JS VMs are). Or you can use the idiom of append() to list and join() after.But reduce() basically forces the inefficient implementation, and I'm sure this is still true in Python 3.---So basically Guido spent a long time debugging a performance problem related to reduce(), and made the decision to eject it, to help users avoid "footguns". I was his officemate at the time, so I recall this, but I wasn't involved directlyAlso, somebody contributed reduce() to Python way back in the 90's, as well as other functional idioms. He wouldn't have added that himself -- it was never his preferred style.He preferred a more imperative style. But he allowed those contributions, and then slightly regretted it later.https://docs.python.org/3/library/functools.html#functools.r...
  • catapart
    For me it's the name[0]. map puts out an array that has been mapped from another array. filter puts out an array that is a filter of the input array. both of those are always true. reduce, on the other hand, may put out a reduction of the input array (probably most of the time), but the fact that it may not means that what is happening is not actually a reduction. In languages like js/ts, you don't even have to return anything of the same type as the input array's elements. You could literally "reduce" and array of integers to a cancellation token, or a state object, or anything else.I realize it's not the most efficient way to work, but I like my code to read like instructions. There's nothing reduce will do that a for loop won't accomplish and the for loop (+ an accumulator, of course) is more clearly "readable" than reduce. If I read map, I know what's going on. If I read filter, I know what's going on. If I read reduce, I have to figure out what's going on, even if I'm pretty sure what is going on. If I could rely on reduce to always give me back an element of the input array, I would use it more. But since it can give back anything, I prefer the simplicity of a for loop.[0] I don't have any suggestions for "better" names because the whole operation is hard to sum up in a word? "dispatch" makes sense, as a function dispatching a function over each element in an array, but it masks the concept of accumulation from return values. "transform" is accurate, but hardly descriptive at all. the list goes on. It's an undeniably useful little function, it's just hard to make it easy to understand and therefore debug.
  • snackbroken
    Map and Filter are nice because they let you reason locally about a single element in isolation. Reduce(Fold) forces you to reason globally about intermediate results. Reduce also forces you to conjure up a "zero" value of the relevant type, which isn't usually difficult but it does constitute some extra mental overhead.
  • hibikir
    You didn't get that complaint in Clojure, just like you wouldn't in Scala or Haskell, is that once you have any expectation that your users know a little bit of category theory, and possibly also thinking in types, it's all quite easy. Even fold is kind of easy, with the more complex signature. But passing [A][A,A =>A] kind of sucks for those that don't think of functional programming. and [A][A,B => A] is even worse. It's often bad enough to get people to build a comparator.Every industry language keeps gaining more and more functional features: Many a new Java version is adding a bunch of scala features with worse syntax. But we don't train people on functional programming at all, so by the time they've built their instincts, passing functions makes no sense to them, immutability is alien, and the idea of a pure function seems irrelevant to them. Thus, they don't get exposed to the building blocks that make reduce seem simple. We always teach them recursion, but the rest? Too little, too late.I could tell you of a bunch of ways to simplify the signature by, say, mandating that one passes a monoid or something like that, but while the signature would be easier, the very same people that are only used to imperative OO will not have an easier time, because they might have studied 2 years of calculus, but they've never even smelled abstract algebra. You can walk out of not just a programming bootcamp, but many a computer science degree without learning a word of this. Therefore, it all remains complicated.
  • el_oni
    Ive only used reduce at work half a dozen times and it does raise an eyebrow each time.But for unioning a bunch of spark dataframes together i think df = reduce(DataFrame.union, list_of_dfs) is much nicer than df, *rest = list_of_dfs for other in rest: df = df.union(other) People just get a bit funny, especially now you have to import it from functools
  • s-zeng
    Even in the world of functional programming, there's an argument to be made that `fold` is a bit of a code smell, in a similar vein as `while` being slightly smelly in an imperative code base. There's good reasons for each to be used, but they are such low level iteration primitives that you might be better off with a higher one (e.g. for loops or iterators in imperative programs; in FP you might reach for monoidic reduces (as opposed to folds where the accumulator is a different type from the list element), monadic traverses, or recursion schemes). Even though you can implement iterators or for loops in terms of while loops, you probably shouldn't, and similar for functional traversals.In languages like python or Java though, you don't really have access to many of the higher power functional traversals however. So that puts you into a similar kind of bind as working in a language with only while loops
  • japgolly
    I assume the author is talking about `fold`, as in `[A] -> B -> ((B,A) -> B) -> B`, and not what I often think of as reduce as `[A] -> ((A,A) -> A) -> A`.`fold` is awesome and super useful. It's the easiest and most convenient way to turn a collection into a single value. Put me anecdotally in the opposite bucket.
  • franey
    At least in TypeScript, it's a bit clunky to type, and I usually forget the order of the reduce function's arguments (accumulator, current item). Maybe it's just me, but it's especially easy to forget the order when the position of the accumulator is the 1st argument to the callback but the 2nd argument of the reduce function: array.reduce( (accumulator, currentItem) => {...}, initialValue, ) In .filter(), The current item is the 1st argument and the intermediate/accumulated value comes later: filter((currentItem, index, intermediateArray)) => ...)I use .filter() more often, so that argument ordering where currentItem is right next to the array is more intuitive for me
  • txhwind
    In imperative languages, it's a leaky abstraction not reducing the cognition overhead, compared with the plain loop.
  • RomanKornev
    Every single `reduce` can be replaced with a more intuitive `groupBy`, `partition`, `mapValues`, `keyBy`, etc.Reduce can approximate anything, that doesn't mean we should use it.My favorite antipattern is items.reduce( (acc, item) => ({ ...acc, [item.id]: item, }), {} ); Like, why? Not only is this ridiculously inefficient O(N^2), it's also longer and less understandable than "build a new map" version.
  • elgertam
    The only part of "hard to read" that has ever made sense is that the callback takes multiple args and sometimes I can't remember the order of the initial value versus the accumulator.Incidentally, reduce is also powerful enough to implement both map and filter in terms of itself, though that's more of a teaching exercise than a good recommendation.I mostly interpret it as of the same spirit with those who oppose proper tail calls because it "ruins" their debugging stack traces.
  • adverbly
    Speaking as someone who often tries to reduce my use of reduce by replacing it with map and filter where possible, for me, falling back to reduce is analogous to falling back to a while loop or a for loop: I avoid it if I can.The problem with reduce is that it can do so much, and therefore it is less clear when reading it quickly what it might be doing.
  • Skeime
    I think this is because in an imperative language, `reduce` does not actually give you much over a `for item in collection` loop. With `map` and `filter`, you immediately learn something about the result (it's a list of the same length as the original, with each item only depending on the corresponding original item; it's a list containing some of the original elements unchanged and nothing else). This is useful, so `map` and `filter` are good.With `reduce`, the result could be anything, and in an imperative language, side effects are also possible. So it's just a loop with worse syntax.(Admittedly, in an imperative language, `map` and `filter` could also have side effects, though I think most people would consider this bad style.)
  • the_other
    (JS/TS is my main language) I love reduce()! It's a hammer/nail method for me. Everything looks like a problem solvable by reduce. (I'm often wrong on that, but I quite enjoy learning why by trying).I really like taking the implementation away from the call site, so that the call site reads const myNewValue = data.reduce(doSomethingMagic); (and then `doSomethingMagic` is defined somewhere else). So simple.I failed a job interview once by using reduce() in a coding test. The reviewer didn't understand why I hadn't used a loop. Loops are easier, for sure, but they sprawl and are open to hacking. They can bring in state from outside the loop. They make the call site long (you always have to read the implementation to learn that you don't need to read it). The same interviewer actively liked to have loop bodies modify the loop conditions (e.g. by taking items out of the source array and decrementing the end condition, so the loop would end earlier). That's the kind of "clever" I find unpredictable and hard to think about. Probably a good thing he rejected me.
  • evnix
    The name itself is confusing to begin with.I come across reduce once in a few months, then I think it's a neat trick and a nice to have function.then I forget it's even available and don't ever use unless these days LLM brings it up again.
  • antfarm
    I remember finally getting what closures and reduce are when I learned Ruby in 2008 for my first Rails job.A pivotal moment on the same level as when I finally understood how recursion and pointers work in 1995 in my first semester CS classes (taught in Modula 2), two concepts I had only ever read about in programming books, but not been able to understand on my own.In 2024 I did Advent of Code in Swift, without using mutable state, custom data types or loops, and used reduce rahther generously. [1][1] https://github.com/search?q=repo%3Aantfarm%2FAdventOfCode202...
  • hungryhobbit
    It's simple really: looping is something we've all done a ton. Map is just a specialized version of something you do all the time, made better/simpler: what's not to like (and learn quickly)?Reduces are used much, much less often. Most devs don't get familiar with them as a result, so every time they have to read a `reduce` they have to re-learn it. And of course, it's a much more involved/complex function, so that exacerbates it.
  • juancn
    I like it conceptually, but the main issue for me with reduce is that it's hard to know exactly how the reduction will actually be executed.The FUBAR potential with map and filter is much smaller, with reduce it depends on deep knowledge of the internals of the reduction itself, which makes it not as useful as a safe abstraction.
  • bunderbunder
    I've seen a lot of technical points about reduce, all of which are true.But I think the real reason might be even simpler: you can't tell what it does just from the name. What `map` does is consistent with well-known programming jargon. What `filter` does is consistent with the word's everyday meaning. But if you don't already know what `reduce` does, it's name isn't even enough to hazard an educated guess.That's not true in Clojure because for lisp programmers for two reasons. First, `reduce` is a ubiquitous and well-known concept in lisp.Second, in most lisps manually doing the same task with imperative code is an ugly verbose eyesore. But in algol-style languages, the imperative alternative is only 1-2 extra lines of very simple code, so using `reduce` is arguably just code golf.
  • slopnt
    I like reduce in principle since it generalizes a simple concept pretty nicely. I don't use it that much in practice since its alternatives just require less brainpower. It competes against using local mutable state with a loop or iterator combinator which I would argue are easier to wrap your head around (i.e. loop with variable/map with closure). I would argue its one of those cases where something is just harder to do/understand in functional vs imperative programming.
  • jakub_g
    I dislike reduce because people sometimes do wild things in the callback that take a lot of mental effort to understand.Sometimes people abuse .map as well to do things that are not obvious (i.e. instead of mapping elements of an array to another array, they modify global variables in a for-loop fashion, and discard the result).But reduce is abused more often and you always need to think really hard if e.g. the initial accumulator is passed or not (it's optional in some languages!), if a correct one is passed (when a compound type is used) and so on.
  • remywang
    I’m so confused, how are you supposed to perform aggregation without reduce? This is like saying “I like plus and times, but I don’t like divide because it’s hard.” I mean sure, but you need it??
  • dochne
    I like it, but it is by far the most ungainly of the three with the most footguns in it's usage.While not as functionally pure, I always appreciate the Ruby each_with_object https://ruby-doc.org/3.4.1/Enumerable.html#method-i-each_wit... as a more pleasant interface for it.
  • felizuno
    IDK, in JS I love reduce and think it is invaluable. If you don't care about closures, never used underscore/lodash, and have not written several hundred var self = this; then you don't share my pain. IMO fat arrow const/let kids don't know about walking uphill to school both ways. Also I agree with commenters who use prev instead of acc, it is much easier on my brain to use prev.TypeScript basically ruined reduce for me though, so there is that.
  • ducaale
    I find `reduce` useful for operations where:- arg1, arg2 and return value are all of the same type e.g `ADD`, `MAX`, `CONCAT` etc- and there is an identity value e.g zero for `ADD`, -math.inf for `MAX`I recommend checking this article[1] on how monoids play nicely with reduce.[1] https://fsharpforfunandprofit.com/posts/monoids-without-tear...
  • keychera
    I came to like reduce when I learned clojure transducers. even in clojure, I always go for looping construct before transducer and then both reduce and transducer just clicked at the same time and I like reduce more now.
  • kevinwang
    In python: I've always thought it's funny that of the list functions (map, filter, reduce?), reduce is the one that was removed, but is the only one that I occasionally reach for. (When I remember it doesn't exist, I'm usually happy to write the more readable three-line for loop.)The other two can be simply expressed as a list comprehension, but afaik you can't with reduce (and if you can, it's probably awful).
  • tantalor
    My favorite gotcha is Java's `Stream.reduce(accumulator)` doesn't call the accumulator if your stream has zero or one elements. This is used for `min(comparator)` and `max(comparator)`. It's very funny when the comparator throws, but only when you have 2 or more elements.
  • xdavidliu
    reduce has complexity to handle the edge case of an empty iterable, and also for the case of a binary function with different types for inputs and outputs. That makes it harder to reason about and "uglier" than map and filter. People probably hate sum and product significantly less, both those also have the edge cases of empty iterable, in which case the natural result is 0 for sum and 1 for product sure, but of what type?While we're on the subject, can someone explain to me why in Rust, you need to annotate the type when you call .sum() on an iterable? For example let p: i32 = [1i32, 2, 3].iter().sum(); println!("hello {}", p); That works, but fails if I replace `p: i32` with `p` or `p: i64`, and I cannot find a satisfactory answer in any thread or llm. The obvious question is why the compiler cannot infer the type from the element type of the container, and the naive response to that is for flexibility summing into a bigger type. But in that case, why would `p: i64` be rejected? And what other type is allowed besides i32?
  • pmontra
    Map maps a value into another value. It's a function call. Easy to understand.Filter picks values according to a rule. It's a select from where condition. Maybe not as easy as map but familiar.Reduce is, what? Even the name is ill fated. Who wants to be reduced? Hence, harder to understand and probably not as common as the other two.
  • sumolessons
    I wanted to add that from personal experience tastes can change! I didn't like reduce when I was first exposed to functional programming, but have come to prefer it.Might be nonsensical, but one thing I sometimes wonder is why I reach for reducing a list to a value more often than I need to generate a list from a starting value. I guess the asymmetry has something to do with the kinds of applications I work on.
  • grommet_kit
    Totally get it. `reduce` feels like a hammer for every nail; often `map` or `filter` makes intent clearer for others.
  • scelerat
    One of the books that most affected my understanding, ability, and joy of programming was Mark Jason Dominus' "Higher Order Perl."So I love reduce, and have for many years.
  • skybrian
    It would be clearer if the operation were part of the name. The most common operations have good names, like sum(), product(), concat(), and so on.If there's no standard function for it, it's trivial to write a utility function.And as part of writing the function, give it a good name and think a bit about the order of operations?So I think reduce() is just unnecessarily generic, unless it's part of a more complicated system like running a map-reduce.
  • Bayard_ne
    Yeah, it usually adds cognitive load for anything beyond basic summation. Simpler to just use a good old `for` loop.
  • very-old-sw
    Reduce with barriers is essential (and amazingly powerful) in parallel functional programming languages like CUDA Thrust.
  • futune
    I was going to write a question asking if reduce is the thing I know as accumulate (I think I picked this up from SICP). But then I went to wikipedia, and it seems that an even more common name is fold.Here's a hypothesis: The fact that the same operation has half a dozen different names makes it sound like there is a lot to learn. If I am totally familiar with fold, and i come upon a reduce, I may need to think more about what's going on, which is distracting.I don't think map and filter have so many synonyms? I know select for filter, but it seems to me less common.
  • altruios
    I'm the weird one here. In JS at least, I reach for reduce before map and filter in most cases. Often it is because I want the accumulator, particularly when I have a list of objects with various properties that I wish to sum together in a reduced object.
  • karmakaze
    It's part of the functional trio: map, filter, reduce--and half of MapReduce.
  • yipinwong
    Reduce introduces state (accumulator), unlike map/filter which normally are used for immutability.I use both, but do not like reduce at all. It's harder to read, yes. But I see the point of using them all.
  • Glyptodon
    It's on my list of things that are awkwardly named because there's not a great name to choose, particularly given how wide the different use cases are.
  • olivewong
    I agree, but I think a lot of it is variable name abuse on the accumulator, making it unclear. I've seen a lot of single letter or worse, a coworker who named it "cum" for short which is super not okay
  • Terr_
    Sure, I'm up for some bike-shedding. [0][1] Unless performance demands otherwise, I prefer map+filter because:1. It's cheaper/faster at communicating intent to humans reading your code. Since a reduce call can do all sorts of interesting things, people need to stare harder to realize "oh, it's just doing a a map and filter together."2. Things are easier to debug. I can vet the process of transformation (and its intermediate results) and then vet the process of excluding some of those results._____With respect to debugging, a sample form Elixir's REPL where the piping (|>) to the dbg() function reveals the intermediate state: iex(1)> [5,34,6,2,7,3,1] |> Enum.map(fn x -> x * x end) |> Enum.filter(fn x -> x < 10 end) |> dbg() [iex:4: (file)] [5, 34, 6, 2, 7, 3, 1] #=> [5, 34, 6, 2, 7, 3, 1] |> Enum.map(fn x -> x * x end) #=> [25, 1156, 36, 4, 49, 9, 1] |> Enum.filter(fn x -> x < 10 end) #=> [4, 9, 1] [0] https://en.wikipedia.org/wiki/Law_of_triviality[1] https://www.smbc-comics.com/comic/noun
  • patwolf
    I've worked with developers that were reduce maximalist. During PR reviews, anything that could be rewritten with reduce was flagged. One of the benefits of AI is not having to care as much about things like that.
  • very-old-sw
    reduce with barriers is essential in parallel functional programming. See the CUDA thrust package.
  • ChrisMarshallNY
    I like it, but I don't use it anywhere near as much as other built-in closures.I find the two ways that you call it to be a bit annoying (not a showstopper). It just seems a bit "kludgy" to me.
  • hyperhello
    For can have another set of variables in the header too. You can simulate it more readably even if you need to call the lambda.
  • agentultra
    In my experience, it depends a lot on the language and the folks you work with. I’ve gotten an eyebrow and a stern talking to for using ‘map’ in JavaScript once. Some people are die-hard about statements and keywords and imperative programming and their world view and be myopic.“We can’t have map in our codebase, we need to be able to hire anyone off the street and have them comfortable in our codebase.”Well… since when did we hire random people off the street?I’m used to functional programming. For me, reduce is perfectly normal. Fewer intermediate variables. No pesky statements, just a nice expression. Great.Buuuut… some languages think implementing tail call optimization is too hard or bad or for ivory tower academics. Or they’re dynamically typed. And then reduce does become difficult to special case and make performant. So even if you like the juice it’s probably not worth the squeeze.It was a great time working with Haskell professionally. I didn’t have to constantly defend my style of programming! But in “everything” languages… well you do. Everyone has to agree on which subset to use. And programmers are like cats. Good luck getting them to agree on anything. Even once you agree there will always be that one challenging the decree.
  • norir
    Anywhere that I could use reduce, I instead write a tail recursive function. This is also why I do not and will not ever choose python or javascript voluntarily.
  • theamk
    At least in Python, I've found that "reduce" is very rarely needed. Most of the times, "sum" is enough, sometimes with "start" values customized (set it to [] to flatten an array for example). It is both easier to read, faster, and needs no imports. It also works great with list comprehensions - "sum(foo(x) for x in input if x > 5)" is much easier to read than reduce equivalent.If you are multiplying, you are likely doing heavy math, and you'll be using numpy - which does not need reduce either.If you are going to return a list of dict, then it's much faster to mutate the results, so using "reduce" will have significant performance implications (unless you want to return input argument, mis-using it as a glorified "for" loop)And if returning not a list/dict, if you can use "min" or "max" or "any" or "all" or "next" (take the first element), then you should use it - it will be easier to read and faster too.So what does this leave us for "reduce"? Frankly, not much. I've only seen it in merging immutable status codes, and that was pretty niche usecase to begin with.(this was all for Python. In other languages without nice list of built-ins reduce might make more sense)
  • billyp-rva
    Well yeah, it's the lowest-level array function. All of the others can be written with reduce, but not vice-versa. Of course it's going to be less friendly.
  • eimrine
    Reduce requires knowing that the sum of zero entities is zero but the multiply of zero entities is one. They forget to throw the correct number and think that reduce() just do not work for them.
  • anon
    undefined
  • g8oz
    I've always like reduce myself, didn't realize others had a negative attitude towards it.
  • mahboi
    Reduce always makes me question the performance and order of operations. The most I'll do in Python is like sum(x[1] for x in args) which is map + reduce. And that's only if x[1] is a number. That's about it. No equivalent in JS. Whenever some JS code has map, I'm like why, and rewrite it as a loop.This is also assuming we're talking about regular code and not an actual map-reduce framework like Spark.
  • WesolyKubeczek
    I like neither of the three and prefer for loops and if statements instead. Yay for shallower stacks!
  • scotty79
    Alternative theory - reduce is badly named.combine, accumulate it aggregate would have way more use.
  • mcphage
    I like it, but don't care for the name. I find it easier to think about in terms of an accumulator.
  • jan_m_savage
    [dead]
  • fatih-erikli-cg
    [dead]
  • anon
    undefined
  • semiinfinitely
    you guys still reading and review code with ur eyes and brain?