Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Transducers
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Amitay Horwitz
December 24, 2023
Programming
0
54
Transducers
Amitay Horwitz
December 24, 2023
Tweet
Share
More Decks by Amitay Horwitz
See All by Amitay Horwitz
Building event sourced systems with Kafka Streams
amitayh
1
1.1k
Event Sourcing with Kafka Streams
amitayh
1
1.2k
Datomic Spotlight
amitayh
0
110
TDD For The Curious
amitayh
0
290
Other Decks in Programming
See All in Programming
15年続くIoTサービスのSREエンジニアが挑む分散トレーシング導入
melonps
2
170
AtCoder Conference 2025
shindannin
0
1k
Smart Handoff/Pickup ガイド - Claude Code セッション管理
yukiigarashi
0
130
AWS re:Invent 2025参加 直前 Seattle-Tacoma Airport(SEA)におけるハードウェア紛失インシデントLT
tetutetu214
2
100
疑似コードによるプロンプト記述、どのくらい正確に実行される?
kokuyouwind
0
380
プロダクトオーナーから見たSOC2 _SOC2ゆるミートアップ#2
kekekenta
0
200
Data-Centric Kaggle
isax1015
2
760
AI時代のキャリアプラン「技術の引力」からの脱出と「問い」へのいざない / tech-gravity
minodriven
20
6.8k
360° Signals in Angular: Signal Forms with SignalStore & Resources @ngLondon 01/2026
manfredsteyer
PRO
0
120
IFSによる形状設計/デモシーンの魅力 @ 慶應大学SFC
gam0022
1
300
CSC307 Lecture 04
javiergs
PRO
0
660
例外処理とどう使い分ける?Result型を使ったエラー設計 #burikaigi
kajitack
16
6k
Featured
See All Featured
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.1k
Speed Design
sergeychernyshev
33
1.5k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
120
Are puppies a ranking factor?
jonoalderson
1
2.7k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
8.6k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Paper Plane (Part 1)
katiecoart
PRO
0
4k
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
97
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
0
270
Crafting Experiences
bethany
1
46
Un-Boring Meetings
codingconduct
0
200
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Transcript
TRANSDUCERS @amitayh
COLLECTION TRANSFORMATIONS We use them all the time…
const double = x => x * 2; const isEven
= x => x % 2 === 0; const coll = [1, 2, 3, 4, 5, 6];
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]
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]
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
Q: What all these have in common? A: We can
de fi ne all of them in terms of reduce ⁉
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, [] ); };
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, [] ); };
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, [] ); };
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, [] ); };
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, [] ); };
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, [] ); };
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)
1⃣
• 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, []);
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]
2⃣
• 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;
• 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;
• 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;
• 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;
• 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;
ENTER: TRANSDUCERS // reducer signature: (whatever, input) => whatever //
transducer signature: reducer => reducer
const transduce = (xf, step, acc, input) => { const
reducer = xf(step); for (let item of input) { acc = reducer(acc, item); } return acc; };
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;
// 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
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
3⃣
• Problem: intermediate results • Solution: function composition! const identity
= x => x; const compose = (...fns) => fns.reduce( (acc, fn) => x => acc(fn(x)), identity );
// 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
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]
OTHER COOL TRANSDUCERS • dropWhile(pred) • partition(pred) • dedupe
EARLY TERMINATION const done = x => ({value: x, __done__:
true});
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; };
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; };
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; };
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]
EARLY TERMINATION • Bonus! we can now use in fi
nite collections function* numbers() { let index = 0; while (true) { yield index++; } }
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]
Q&A 🤓
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: מםכאמם