‹ BackHN Continuity

Thread

Anecdotally, programmers dislike "reduce"

175 points · 283 comments · vinhnx

  1. Skeime · · focus · HN ↗
    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.)

    1. Hackbraten · · focus · HN ↗
      I think that in every imperative language that offers `map`, `filter`, `reduce`, or similar, the written contract of this API should state that any higher-order function handed to it as an argument must be free from side effects.

      I think I’ve seen several language core APIs have this in their contract, e.g. `Stream#reduce` in Java [0] (emphasis mine):

      > accumulator - an *associative, non-interfering, stateless* function for combining two values

      [0]: <a href="https:&#x2F;&#x2F;docs.oracle.com&#x2F;javase&#x2F;8&#x2F;docs&#x2F;api&#x2F;java&#x2F;util&#x2F;stream&#x2F;Stream.html#reduce-T-java.util.function.BinaryOperator-" rel="nofollow">https:&#x2F;&#x2F;docs.oracle.com&#x2F;javase&#x2F;8&#x2F;docs&#x2F;api&#x2F;java&#x2F;util&#x2F;stream&#x2F;S...

      1. Someone · · focus · HN ↗
        Even though it makes print debugging harder, I think it would be better if the language enforced such a contract.
      2. Skeime · · focus · HN ↗
        I mean, most of the code that I write would be side-effect free anyway. In an imperative loop, this would also be true except for updating local variables. If this is the case, `reduce` really is the same as a loop over a collection, except that the names for the state passed between iterations come out better. In the `reduce` version, you can name the parameters to the reducer, but often not the return values. As a reader, one needs to connect the return values to the parameters by position.

        (Note that by &quot;loop over a collection&quot;, I explicitly mean a looping construct that gives the elements of the collection directly, instead of looping over indices and extracting the elements manually.)

      3. speedstyle · · focus · HN ↗
        In Rust these specifically take `FnMut`, a function which can update internal&#x2F;borrowed state, rather than `Fn` which can&#x27;t easily. In `map` or `filter` you shouldn&#x27;t rely on the iteration order so that&#x27;s not often useful – maybe something &#x27;logically&#x27; stateless but which needs a mutable connection&#x2F;threadpool&#x2F;cache, or eg a counter which is really an ancillary reduction. There&#x27;s even `inspect` which is explicitly for such side effects. In `fold`, the order is guaranteed and you could use it for a state machine, a fiddly `zip` with other mutable iterators, etc – something you need to perform the reduction, but which isn&#x27;t really an output, I think you could reasonably write either

            .fold(init, move |acc, x| {…})  &#x2F;&#x2F; or
            .fold((state, init), |(state, acc), x| {…}).1
      4. KPGv2 · · focus · HN ↗
        &gt; I think that in every imperative language that offers `map`, `filter`, `reduce`, or similar, the written contract of this API should state that any higher-order function handed to it as an argument must be free from side effects.

        Does the written contract for for-loops specify this as well? Because that&#x27;s the obvious alternative for a reduce that an imperative programmer would reach for: a for-loop with the programmer managing the accumulation manually.

        I also don&#x27;t see why a reduce should be side-effect free. There&#x27;s nothing wrong with having a type def&#x27;t for reduce be

            base.data.List.foldLeft : (b -&gt;{e} a -&gt;{e} b) -&gt; b -&gt; [a] -&gt;{e} b
        
        Where {e} indicates a side effect. Here, the reducer can have side effects, and those side effects propagate to the result of the fold&#x2F;reduce.
Open on Hacker News to reply ↗

Unofficial Hacker News client; not affiliated with Y Combinator.