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
Amitay Horwitz
December 24, 2023
Programming
0
55
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
生成 AI 時代のスナップショットテストってやつを見せてあげますよ(α版)
ojun9
0
230
Claude Code の Skill で複雑な既存仕様をすっきり整理しよう
yuichirokato
1
400
技術検証結果の整理と解析をAIに任せよう!
keisukeikeda
0
120
Vuetify 3 → 4 何が変わった?差分と移行ポイント10分まとめ
koukimiura
0
150
Agentic AI: Evolution oder Revolution
mobilelarson
PRO
0
180
LangChain4jとは一味違うLangChain4j-CDI
kazumura
1
190
AWS Infrastructure as Code の新機能 2025 総まとめ 〜SA 4人による怒涛のデモ祭り〜
konokenj
10
3.4k
S3ストレージクラスの「見える」「ある」「使える」は全部違う ─ 体験から見た、仕様の深淵を覗く
ya_ma23
0
620
ベクトル検索のフィルタを用いた機械学習モデルとの統合 / python-meetup-fukuoka-06-vector-attr
monochromegane
2
460
コーディングルールの鮮度を保ちたい / keep-fresh-go-internal-conventions
handlename
0
200
ふつうの Rubyist、ちいさなデバイス、大きな一年
bash0c7
0
990
AI時代のシステム設計:ドメインモデルで変更しやすさを守る設計戦略
masuda220
PRO
5
1k
Featured
See All Featured
Music & Morning Musume
bryan
47
7.1k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.4k
The Curious Case for Waylosing
cassininazir
0
270
Ethics towards AI in product and experience design
skipperchong
2
220
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
120
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
180
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
550
Information Architects: The Missing Link in Design Systems
soysaucechin
0
830
The Pragmatic Product Professional
lauravandoore
37
7.2k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
140
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
480
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: מםכאמם