1. 30
  1. 2

    Since transducers are often returned from special forms of functions, like those used in threading, I guess that’s why I never understood why I might bother with them.

    Do they only save time if you set them up before use and then use the same definition over and over again? Because that sounds like a function on its own.

    1. 3

      Do they only save time if you set them up before use and then use the same definition over and over again?

      If you do

      (->> seq
        (map inc)
        (map inc)
        (filter even?))
      

      it will create two intermediate sequences (one for each inc stage) and finally return a third sequence from the even? step. Granted it won’t allocate & compute the whole sequence for each stage because they use ChunkedSeq, it still will do allocations which are thrown away or replaced in the next stage.

      Transducers skip the intermediate sequences. It’s a performance optimization. Using transducers for the above pipeline is equal to (map+filter #(let [x (inc (inc %))] (if (even? x) x nil)) seq). The biggest benefit is that transducers compose just as nicely as sequence-operations + ->> do.

      Since transducers are often returned from special forms of functions, like those used in threading, I guess that’s why I never understood why I might bother with them.

      The functions used with the threading operator ->> are the “normal” forms and not some special implementation:

      ->> just rewrites (->> seq (map inc) (map dec) (filter even?)) to (filter even? (map inc (map inc seq))). Nothing special is needed from map or filter.

      The functions with arities n-1 (skipping the seq arg) return the transducer-versions.

      1. 1

        So by dropping that initial argument,the array, that could end up with threading to compose a transducer.

        1. 1

          Sadly not:

          (->> 
            (filter even?)
            (map inc))
          

          will expand to

          (map inc (filter even?))
          

          which invokes the arity-2 version of map which expects a seq as its second argument. However, the value returned by (filter even?) is a transducer and map will throw an error.

          1. 3

            Clojure seems to repeat all the mistakes Scala made in this regard, just without types and 10 years later…

            1. 2

              At least all of it is easier to write, and the language at least is extensible without obsoleting old code…

              1. 6

                Agreed, having no types makes it easier due to not having to deal with HKTs, variance etc.

                But “extensible without obsoleting old code” isn’t really a selling point here:

                In both cases it’s library functionality that is so core to the language’s usage experience that defaults really matter – nobody is going to use “the better way” if “the obsolete way” is taking up the language’s prime real estate.

                What you really want to do is to “upgrade” the semantics from the worse model to the better model, and most languages/tooling have no solution for that.

            2. 1

              So part of the problem I see is transduce not composing like the other transducers might. Is that right?

              I love threading. It makes so much code so much easier to read. It would be a shame to give it up just because it’s somewhat less efficient.

      2. 1

        More readable for you perhaps, but someone else might be confused by the new macro they don’t recognise.

        1. 2

          Threading macros are widely used in clojure. If a beginner hasn’t seen them yet they will learn.