Because you apply the composed transducers (f,g,h
) to a reducer (r
), the leftmost (f
) becomes topmost, first to act on the eventual arguments (acc, el)
to the augmented reducer f( g(h(r)) )
:(*)
compose(f,g,h)(r)(acc, el) =~= f( g( h( r)) )(acc, el)
<--------
--...........->
Indeed the rightmost, h
, is applied to the argument r
first; but that r
is also a function; the whole composed thing is a function; and the composed function has f
as its first component, so it is f
that gets to work on the next arguments (acc, el)
first, using the result of the inner composition g( h( r))
according to the definition of f
.
What we have here, is
r // reducer
h // transducer
h( r ) // reducer
g // transducer
g(h( r )) // reducer
f // transducer
f(g(h( r ))) // reducer
(*) So you have defined your functions so that
mapReducer(foo)(reducer)(acc, el)
=~= reducer(acc, foo(el));
filterReducer(predicate)(reducer)(acc, el)
=~= predicate(el) ? reducer(acc, el) : acc;
concatReducer(acc, el)
=~= acc.concat(el);
and, most importantly,
compose(f,g,h,...,p,q)(r)
=~= [f,g,h,...,p,q].reduceRight((acc, fn) => fn(acc), r);
=~= f(g(h(...(p(q( r ))...)))
So then
[1, 2, 3]
.reduce(
compose(
f, // f = mapReducer(times2),
g, // g = filterReducer(isGreaterThanThree),
h // h = mapReducer(add1),
)(r), // r = concatReducer
[]
)
=~= [1, 2, 3] .reduce( f(g(h(r))), []) // rc = f(g(h(r)))
=~= rc( rc( rc( [], 1), 2), 3)
=~= rc( rc( f(g(h(r)))( [], 1), 2), 3)
=~= rc( rc( mapReducer(times2)(g(h(r)))( [], 1 ), 2), 3)
// mapReducer(foo )(reducer)(acc, el)
// =~= reducer( acc, foo(el))
=~= rc( rc( g(h(r))([], times2( 1)), 2), 3)
=~= rc( rc( filterReducer(isGreaterThanThree)(h(r))([] , times2( 1)),
2),
3)
// filterReducer(pred )(reducer)(acc, el)
// =~= pred(el) ? reducer( acc, el) : acc
=~= rc( rc( isGreaterThanThree( twice1) ? h(r)( [], twice1) : [],
2), /* where twice1 = times2(1)
h = mapReducer( add1) */
3)
=~= rc( rc( isGreaterThanThree( twice1) ? r([], add1(twice1)) : [],
2), /* where twice1 = times2(1)
r = concatReducer */
3)
=~= rc( rc( isGreaterThanThree( twice1) ? [].concat(add1(twice1)) : [],
2), /* where twice1 = times2(1) */
3)
=~= ...
and we can see that mapReducer(times2)
gets to work on the list's elements first, before the filtering by isGreaterThanThree
and then the mapping of add1
.
In processing the [1,2,3]
, first the mapping of times2
is done (implicitly as-if making it [2,4,6]
), then the filtering (which would leave just [4,6]
), and then the mapping of add1
, for the final result of [5,7]
indeed.
There's no creating the interim structure though. Instead one composite reducer is built, and used by one reduce
, advancing along the input one step at a time.
Thus transducers are a fusion / deforestation technique. Intuitively, nested folds should fuse, eliminating the need for the interim structure creation by an inner fold only so it can be consumed by the wrapping fold; following the transducer discipline lets us achieve just that.
So then, mapReducer
and filterReducer
are badly named. They are actually mappingTransducer_Maker
(or just mapping
) and filteringTransducer_Maker
(or just filtering
), where
mapping(foo)
is a mapping transducer that applies foo
to the input's element elt
, resulting in foo(elt)
, and
filtering(pred)
is a filtering transducer that filters out the input's element el
according to the predicate call pred(el)
, or keeps it in.
Or rather, a transducer augments its argument reducer
so that when the combined, augmented reducer is finally called with the accumulator and the current element, it manipulates the element and the accumulator in a prescribed way before passing the results to the base reducer
:
mapping(foo)( filtering(pred)(reducer) )( acc, elt)
----------------------------------------( )
=~=
filtering(pred)(reducer)( acc, foo(elt) )
=~=
pred( foo(elt) ) ? reducer( acc, foo(elt) ) : acc
So, mapping(foo)
and filtering(pred)
are composed right-to-left with respect to their argument, reducer
; but then the composed reducer starts working from the top, tracing the transducers' effects left-to-right -- first doing the mapping, and doing the filtering after that.