Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Transducers

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 Transducers

Avatar for Amitay Horwitz

Amitay Horwitz

December 24, 2023
Tweet

More Decks by Amitay Horwitz

Other Decks in Programming

Transcript

  1. const double = x => x * 2; const isEven

    = x => x % 2 === 0; const coll = [1, 2, 3, 4, 5, 6];
  2. const double = x => x * 2; const isEven

    = x => x % 2 === 0; const coll = [1, 2, 3, 4, 5, 6]; const doubled = coll.map(double); // [2, 4, 6, 8, 10, 12]
  3. const double = x => x * 2; const isEven

    = x => x % 2 === 0; const coll = [1, 2, 3, 4, 5, 6]; const doubled = coll.map(double); const even = coll.filter(isEven); // [2, 4, 6]
  4. const double = x => x * 2; const isEven

    = x => x % 2 === 0; const coll = [1, 2, 3, 4, 5, 6]; const doubled = coll.map(double); const even = coll.filter(isEven); const sum = coll.reduce( (acc, item) => acc + item, 0 ); // 21
  5. Q: What all these have in common? A: We can

    de fi ne all of them in terms of reduce ⁉
  6. const map = (coll, f) => { return coll.reduce( (acc,

    item) => [...acc, f(item)], [] ); }; const filter = (coll, pred) => { return coll.reduce( (acc, item) => pred(item) ? [...acc, item] : acc, [] ); };
  7. const map = (coll, f) => { return coll.reduce( (acc,

    item) => [...acc, f(item)], [] ); }; const filter = (coll, pred) => { return coll.reduce( (acc, item) => pred(item) ? [...acc, item] : acc, [] ); };
  8. const map = (coll, f) => { return coll.reduce( (acc,

    item) => [...acc, f(item)], [] ); }; const filter = (coll, pred) => { return coll.reduce( (acc, item) => pred(item) ? [...acc, item] : acc, [] ); };
  9. const map = (coll, f) => { return coll.reduce( (acc,

    item) => [...acc, f(item)], [] ); }; const filter = (coll, pred) => { return coll.reduce( (acc, item) => pred(item) ? [...acc, item] : acc, [] ); };
  10. const map = (coll, f) => { return coll.reduce( (acc,

    item) => [...acc, f(item)], [] ); }; const filter = (coll, pred) => { return coll.reduce( (acc, item) => pred(item) ? [...acc, item] : acc, [] ); };
  11. const map = (coll, f) => { return coll.reduce( (acc,

    item) => [...acc, f(item)], [] ); }; const filter = (coll, pred) => { return coll.reduce( (acc, item) => pred(item) ? [...acc, item] : acc, [] ); };
  12. OBSERVATIONS 👀 1. The actual logic is in the reducing

    function - the rest is boilerplate 2. We are coupled to our input and output types 3. Chaining several operations will introduce intermediate results - wasteful: coll.map(double).filter(isEven)
  13. • Problem: logic in the reducing function • Solution: extract

    it const map = f => (acc, item) => [...acc, f(item)]; const filter = pred => (acc, item) => pred(item) ? [...acc, item] : acc; const run = (coll, reducer) => coll.reduce(reducer, []);
  14. const coll = [1, 2, 3, 4, 5, 6]; run(coll,

    map(double)); // [2, 4, 6, 8, 10, 12] run(coll, filter(isEven)); // [2, 4, 6]
  15. • Problem: coupling to input and output • Solution: inject

    the “step” function const map = f => (acc, item) => [...acc, f(item)]; const filter = pred => (acc, item) => pred(item) ? [...acc, item] : acc;
  16. • Problem: coupling to input and output • Solution: inject

    the “step” function const map = f => (acc, item) => [...acc, f(item)]; const filter = pred => (acc, item) => pred(item) ? [...acc, item] : acc;
  17. • Problem: coupling to input and output • Solution: inject

    the “step” function const map = f => step => (acc, item) => [...acc, f(item)]; const filter = pred => step => (acc, item) => pred(item) ? [...acc, item] : acc;
  18. • Problem: coupling to input and output • Solution: inject

    the “step” function const map = f => step => (acc, item) => step(acc, f(item)); const filter = pred => step => (acc, item) => pred(item) ? step(acc, item) : acc;
  19. • Problem: coupling to input and output • Solution: inject

    the “step” function const map = f => step => (acc, item) => step(acc, f(item)); const filter = pred => step => (acc, item) => pred(item) ? step(acc, item) : acc;
  20. const transduce = (xf, step, acc, input) => { const

    reducer = xf(step); for (let item of input) { acc = reducer(acc, item); } return acc; };
  21. const transduce = (xf, step, acc, input) => { const

    reducer = xf(step); for (let item of input) { acc = reducer(acc, item); } return acc; }; // Step functions const into = (acc, item) => [...acc, item]; const sum = (acc, item) => acc + item;
  22. // Some transducer const xf = filter(isEven); const coll =

    [1, 2, 3, 4, 5, 6]; transduce(xf, into, [], coll); // [2, 4, 6] transduce(xf, sum, 0, coll); // 12
  23. FULL DECOUPLING 😎 • The process is separate from the

    input / output sources • Reuse transformation logic • Built in collections (arrays, objects) • Custom collections (Immutable.js) • WebSockets / In fi nite streams
  24. • Problem: intermediate results • Solution: function composition! const identity

    = x => x; const compose = (...fns) => fns.reduce( (acc, fn) => x => acc(fn(x)), identity );
  25. // Composing transducers const xf = compose( filter(isEven), map(double) );

    const coll = [1, 2, 3, 4, 5, 6]; // No intermediate results! transduce(xf, into, [], coll); // [4, 8, 12] transduce(xf, sum, 0, coll); // 24
  26. STATEFUL TRANSDUCERS • Example: drop - remove fi rst n

    elements const drop = n => step => { let remaining = n; return (acc, item) => (remaining-- > 0) ? acc : step(acc, item); }; coll = [1, 2, 3, 4, 5, 6]; transduce(drop(4), into, [], coll); // [5, 6]
  27. EARLY TERMINATION const done = x => ({value: x, __done__:

    true}); const transduce = (xf, step, acc, input) => { const reducer = xf(step); for (let item of input) { acc = reducer(acc, item); } return acc; };
  28. EARLY TERMINATION const done = x => ({value: x, __done__:

    true}); const transduce = (xf, step, acc, input) => { const reducer = xf(step); for (let item of input) { acc = reducer(acc, item); if (acc.__done__) { acc = acc.value; break; } } return acc; };
  29. EARLY TERMINATION const done = x => ({value: x, __done__:

    true}); const transduce = (xf, step, acc, input) => { const reducer = xf(step); for (let item of input) { acc = reducer(acc, item); if (acc.__done__) { acc = acc.value; break; } } return acc; };
  30. EARLY TERMINATION • Example: take - keep fi rst n

    elements const take = n => step => { let remaining = n; return (acc, item) => (remaining-- > 0) ? step(acc, item) : done(acc); }; transduce(take(2), into, [], coll); // [1, 2]
  31. EARLY TERMINATION • Bonus! we can now use in fi

    nite collections function* numbers() { let index = 0; while (true) { yield index++; } }
  32. EARLY TERMINATION • Bonus! we can now use in fi

    nite collections const xf = compose( filter(isEven), map(double), take(5) ); transduce(xf, into, [], numbers()); // [0, 4, 8, 12, 16]
  33. RESOURCES 📚 • Blog post: http://wix.to/G8DRABw • Talk by Rich

    Hickey: http://wix.to/XMDRABw • Transducers in Scala: http://wix.to/XsDRABw • …And in JavaScript: מםכאמם