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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Amitay Horwitz
December 24, 2023
Programming
59
0
Share
Transducers
Amitay Horwitz
December 24, 2023
More Decks by Amitay Horwitz
See All by Amitay Horwitz
Building event sourced systems with Kafka Streams
amitayh
1
1.2k
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
次世代リンターで探る、tsgo 時代における型認識カスタムルールの現実解
ytakahashii
3
1.4k
開発体験を左右するライブラリの API 設計 - GraphQL スキーマ構築ライブラリから考える #tskaigi
izumin5210
2
1.4k
CLIであることを活かしたGitHub Copilot CLI活用術 / GitHub Copilot CLI Pro Tips & Tricks
nao_mk2
1
1.2k
ECR拡張スキャンでSBOMを収集して サプライチェーン攻撃の影響調査を 爆速で終わらせてみた
akihisaikeda
2
210
Spec-Driven Development with AI-Agents: From High-Level Requirements to Working Software
antonarhipov
2
400
3Dシーンの圧縮
fadis
1
550
[KCD Czech] eBPF Meets the GPU: Future of AI Infra Observability
doniacld
0
130
The Arts and Crafts of Work in the AI Era — Toward Mastery in Software Development
kuranuki
1
680
oxlintはeslint/typescript-eslintを置き換えられるのか
shomafujita
2
290
Modding RubyKaigi for Myself
yui_knk
0
830
関係性から理解する"同一性"の型用語たち
pvcresin
2
620
Signal Forms: Beyond the Basics @ngBaguette 2026 in Paris
manfredsteyer
PRO
0
190
Featured
See All Featured
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
460
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Facilitating Awesome Meetings
lara
57
6.9k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
370
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
150
Believing is Seeing
oripsolob
1
140
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Thoughts on Productivity
jonyablonski
76
5.2k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
190
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
150
Speed Design
sergeychernyshev
33
1.8k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
220
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: מםכאמם