RamdaJS: transduce
Tags: javascript, ramda, functional, transducers
Published: 2020-04-03 08:41:16

When I was looking at the Ramda docs I came across the [transduce](https://ramdajs.com/docs/#transduce) function. And at first I found it rather difficult to understand what exactly the function did and what you could do with it. So I did some reading about **transducers** in general and started trying some things to fully understand. In this post I will describe the steps I took towards fully understanding this **transduce** function.

# What is a transducer?
First things first. What are transducers and what can we do with them? Well, you may have guessed it already, but the word **transduce** is just a combination of the words **transform** and **reduce**. And that is also what it does:

> A transducer **transforms** items while **reducing** them.

*I assume you have an understanding of what reducers are. If not, there are a lot of [resources](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) out there that will help you and it's not that difficult.*

# Doc example
Now we have some idea of what a transducer does, we can have a look at the example code from the [Ramde docs](https://ramdajs.com/docs/#transduce) and try to understand:

```js
const numbers = [1, 2, 3, 4];
const transducer = R.compose(R.map(R.add(1)), R.take(2));

R.transduce(transducer, R.flip(R.append), [], numbers); //=> [2, 3]
```

So what is happening here? 

- The first 2 lines are pretty clear, I think. We declare an array of numbers and create a transducer function which is just a composed function of:
 - **R.map(R.add(1))**: map over an array and add 1 to each element
 - **R.take(2)**: take the first 2 elements of an array
 - We use **R.compose** here so it will perform a **right-to-left** composition, i.e. first **take** and then **map**.
- The last line is where we are going to use the **transduce** function. The function accepts 4 arguments:
 - **The transducer**: function that performs the transformation
 - **The iterator**: in our case we will **append** the **current value** to the **accumulator**
 - **The initial value**
 - **The list to iterate**

When we run this example code the result will be an array of **[2, 3]**. And that is understandable because in the **composed transducer** function we:

- **Take** the first 2 elements of the array -> **[1, 2]**
- **Add 1** to each element -> **[2, 3]**

But now you may ask yourself: What is the difference then with just running the composed transducer function with the numbers array? That will have the same result, right? Yes, it has!

```js
// Only running the transducer function with the numbers array will return the same result
transducer(numbers); //=> [2, 3]
```

So, why are we using this `R.transduce` function and not just the composed transducer function? **What is the added value of using `R.transduce`?**

# Benefits of `R.transduce`
I found this point confusing at first, but it's pretty simple if you understand. Because the benefit of using transduce is **performance** :tada:

> Transducers are all about **replacing multiple trips** through the same data **with a single one**.

So with using `R.transduce` the composed transducer function will be used in a different **optimised** way, where the array is only **iterated once**! We can make that clear by replacing the `take` with another `add` and by adding some logs to the transducer function:

```js
const transducer = R.compose(
  R.tap(x => console.log('LEFT')),
  R.tap(x => console.log('ADD 1 to', x)),
  R.map(R.add(1)),
  R.tap(x => console.log('ADD 2 to', x)),
  R.map(R.add(2)),
  R.tap(x => console.log('RIGHT'))
);
```

Now you will see a difference in output when using the transducer directly and when using it with `R.transduce`.

```js
transducer(numbers); //=> [4, 5, 6, 7]
// RIGHT
// ADD 2 to [3, 4, 5, 6]
// ADD 1 to [4, 5, 6, 7]
// LEFT
```

Understandable and as expected:

- Iterate over the array and add 2
- Iterate over the array (**again!**) and add 1

Now, can you guess what `R.transduce` will output when using our modified transduce function? Because it still had a surprise for me when running it for the first time. Let's see:

```js
R.transduce(transducer, R.flip(R.append), [], numbers); //=> [4, 5, 6, 7]
// LEFT
// ADD 1 to 1
// ADD 2 to 2
// RIGHT

// LEFT
// ADD 1 to 2
// ADD 2 to 3
// RIGHT

// ... and the same for the numbers 3 and 4
```

What we can see clearly now is that **the array is only iterated once**. Each element is passed to the transformer function before moving to the next element. So that is the **performance** benefit we were talking about.

But what you also must notice in the output is that `R.transduce` is performing the operations **left-to-right** instead of right-to-left what you would expect when using `R.compose`. And that is just something you **must know** about transducers:

> Transducers compose in an opposite order.

So, using `R.compose` with a transducer performs **left-to-right** and using `R.pipe` with a transducer performs **right-to-left**. The exact **opposite** when using normal function composition.

# Conclusion
Reading the Ramda docs about `R.transduce` can be very confusing at first. But in the end it is not that difficult to understand what the function does. At least, I hope you feel the same after reading this post.

Transducers can just be very useful if you need to combine a number of operations (`map`, `take`, `filter`) over a list. With a transducer it's then possible to **optimize** this process and keep your code **clean**.

If you still have any questions about transducers, don't hesitate :smile:

**Cheers**