Our helpers are now powerful enough to work with any data type as the source, but they can only build up arrays or objects as the target. For an arbitrary collection type, we thus have to fall back to our transduce
function and manually specify the inner reducer behavior based on the API of our target collection.
In this lesson, we'll add support for the transducer protocol, and see how it enables us to transduce into any collection type without having to fall back to using transduce
. We'll use the List
type from Immutable.JS as our example.
Instructor: [00:00] To see how our helpers work with custom data types, let's use a list from immutable-js. I'm just importing list from immutable, and let's use inter to create an array. We're going to use this double and even transform, which will only include a value if it's even, and then double it.
[00:20] Let's add that in, but then as a source, let's call list with some values. This will create an immutable list from this source array. If we run this, we get our expected results of four and eight. This works fine, since this list is an iterable, but if we reverse this, I'm just going to copy it.
[00:42] Let's use a normal array as a source, but make the list our target type. Then our inter helper will throw our error, since the list is neither an array nor an object. We'll get the same problem if we called seek. Let's call that instead, and make the list our source.
[01:01] Again, we get this error. We could solve this by creating an array, and then using the from-js utility to create our list. We're already importing from-js, as we can see here. Let's wrap our call to inter with it.
[01:18] Now, we have an immutable list again, but it's a bit hard to see. Let's call the toString method to make that easier. There we go. This works, but it's annoying to have to add this boilerplate everywhere. It would be much nicer if our helpers knew how to transduce over any data type.
[01:36] Let's use some dependency inversion and code against a protocol instead. Our helpers need to be able to determine this in the reducer as well as the initial value. We'll add two methods that our data types can implement to support this.
[01:54] Let's do that check here, and we'll call the inner reducer step. We'll add in a condition, and we want to check if step is defined on our collection. We'll also namespace this under a transducer string. That takes care of the inner reducer.
[02:11] Then for our seed value, we'll save that under a key called init. I'm going to define a const called init, and if that exists on our collection, we'll use it. I'll just put this on a new line. Whatever doesn't exist will fall back to the constructor.
[02:33] Then we need to call it up here as well. With that defined, we can return a call to transduce with our transform, the inner reducer, our initial seed, and our collection. Before this will work for our example, we also have to define these methods on the list prototype.
[02:52] Let's define list.prototype, and we'll start with our step value. This will be a reducer taking the list and value, and it's going to return the result of pushing on the value to the list. This works because the push method returns a new array with a value added in.
[03:13] We also need to define the init function. All that will do is create a new list. We'll call seek, just like we did before, and now, we get back a list as our result. Let's call toString on it again. Now, that we know this works, let's do the same modification to our inter helper.
[03:34] I'm just going to copy this whole block, but instead of checking against the collection, we want to check the two argument. Now, we should be able to use inter with a list as our target type. That works as expected.
[03:54] These protocol methods we've added in are actually already defined in what's called the transducer protocol. By adding them, you can use custom data types with any popular transducer library, as they all support this protocol.
[04:09] The two that I've used are transducers.js by James Long, and the other one is transducers-js by Cognitect Labs. Both of these repos have more info about their transducer protocol in their readme as well.
[04:23] They also define a result method, which is used for any final operation on the collection once it's been iterated through, but we didn't really need it for our examples.