$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Transducers
Search
Amitay Horwitz
December 24, 2023
Programming
0
48
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.1k
Datomic Spotlight
amitayh
0
100
TDD For The Curious
amitayh
0
290
Other Decks in Programming
See All in Programming
How Software Deployment tools have changed in the past 20 years
geshan
0
29k
Microservices Platforms: When Team Topologies Meets Microservices Patterns
cer
PRO
1
1k
【Streamlit x Snowflake】データ基盤からアプリ開発・AI活用まで、すべてをSnowflake内で実現
ayumu_yamaguchi
1
120
チームをチームにするEM
hitode909
0
310
Giselleで作るAI QAアシスタント 〜 Pull Requestレビューに継続的QAを
codenote
0
150
TestingOsaka6_Ozono
o3
0
130
SwiftUIで本格音ゲー実装してみた
hypebeans
0
160
AWS CDKの推しポイントN選
akihisaikeda
1
240
20251212 AI 時代的 Legacy Code 營救術 2025 WebConf
mouson
0
100
手軽に積ん読を増やすには?/読みたい本と付き合うには?
o0h
PRO
1
170
【CA.ai #3】ワークフローから見直すAIエージェント — 必要な場面と“選ばない”判断
satoaoaka
0
240
複数人でのCLI/Infrastructure as Codeの暮らしを良くする
shmokmt
5
2.3k
Featured
See All Featured
A better future with KSS
kneath
240
18k
Embracing the Ebb and Flow
colly
88
4.9k
Mobile First: as difficult as doing things right
swwweet
225
10k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
253
22k
The Language of Interfaces
destraynor
162
25k
Build The Right Thing And Hit Your Dates
maggiecrowley
38
3k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
37
2.6k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
132
19k
Practical Orchestrator
shlominoach
190
11k
Writing Fast Ruby
sferik
630
62k
Become a Pro
speakerdeck
PRO
31
5.7k
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: מםכאמם