|
A key component to transducers is to realize that map, filter, take, can all be expressed as special cases of the reduce function. So a curried map function can be expressed as a reduction operation, for example, in python you could do.. # map that adds 1 to each element
# same as curried map(sub1)([1,2,3])
sub1 = lambda x: x - 1
map(sub1, [1,2,3])
# same outcome using reduce
reduce(lambda acc, x: acc + [sub1(x)], [1,2,3], [])
Notice that when writing a map operation using the reduce function, we've put two pieces of logic on each step. (1) we have a transformation called sub1, and (2) we have a step operation (returning an array with the transformed x appended to it).We can use this same logic to create filters with an if statement.. lt2 = lambda x: x < 2
filter(lt2, [1,2,3])
reduce(lambda acc, x: acc + [x] if lt2(x) else acc, [1,2,3], [])
A critical thing to notice in each of these cases is that if we ran a curried filter and map (with transformation / filtering functions already passed), then they would loop over the data twice. However, if you look at the example on this github repo, the want to* take 3 elements from the sequence * perform a transformation (map operation) to those elements Notice that in this case take is being performed before the map, but you could imagine doing something like * map items from geometric series to float * filter items that are greater than .15 * take 3 of the remaining items How would you do that with map and filter functions without looping over the entire (inexhaustible) sequence? This is the beauty of transformers and transducers, they provide a composable approach to filtering, transforming, and taking elements from a (possibly infinite) sequence. Of course, all of this could be done in a for loop, but often it introduces complexity.. # assume geom_series is the generator from the github example
take = 3
for el in geom_series:
if float(el) < .15:
result.append(el)
if len(result) == take: break
However, this for loop is using the same ideas: we have a transformation / if (predicate) operation, and step operation to append the data we want. The problem transducers try to solve is writing a series of these types of operations compactly and cleary as a pipeline of functions (erm, transformers), so those functions can be replaced/moved around quickly, and the pipelines can be combined together simply.A good article that covers tranducers in javascript (sorry!) is here: http://simplectic.com/blog/2014/transducers-explained-1/ |
But isn't this just a matter of defining map and filter over a lazy stream datatype (aka iterators) instead of over lists?
So the workflow would be
This would let you run the filters "in parallel" without iterating through things twice.