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
TypeScriptでType Match的なことをする話 #すえなみチャンス暑気払い
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
kyo_ago
August 04, 2019
Programming
1.4k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
TypeScriptでType Match的なことをする話 #すえなみチャンス暑気払い
kyo_ago
August 04, 2019
More Decks by kyo_ago
See All by kyo_ago
フロントエンドの リソース管理の話 TechFeed Summit#1 #techfeed #techfeedsummit
kyo_ago
5
2k
WebReplayから見るWeb開発の未来 #builderscon
kyo_ago
2
1k
今日から始めるbugbounty
kyo_ago
0
320
karmaを使ったSPA向けE2Eテスト技法
kyo_ago
6
5.7k
E2Eという名称の指すもの
kyo_ago
0
2.7k
How to use Scala.js in real world?
kyo_ago
1
2.2k
Other Decks in Programming
See All in Programming
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
200
Vite+ Unified Toolchain for the Web
naokihaba
0
730
どこまでゆるくて許されるのか
tk3fftk
0
470
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
140
フィードバックで育てるAI開発
kotaminato
1
110
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
240
AIキャラアプリkaiwaの低遅延音声通話基盤をどう作ったか - AWS Gravitonで支える低遅延・低コストAI Agent基盤
mogamit
0
170
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
12
4.7k
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
160
音楽のための関数型プログラミング言語mimiumにおける多段階計算の活用
tomoyanonymous
1
300
Haskell/Servantを通してWebミドルウェアを捉え直す
pizzacat83
1
480
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
220
Featured
See All Featured
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
260
Faster Mobile Websites
deanohume
310
32k
Google's AI Overviews - The New Search
badams
0
1.1k
Are puppies a ranking factor?
jonoalderson
1
3.7k
Technical Leadership for Architectural Decision Making
baasie
3
440
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
3
1.1k
A designer walks into a library…
pauljervisheath
211
24k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
210
Git: the NoSQL Database
bkeepers
PRO
432
67k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
180
Transcript
5ZQF4DSJQUͰ5ZQF.BUDIత ͳ͜ͱΛ͢Δ ͑͢ͳΈνϟϯεॵؾ͍ !LZP@BHP
݁
TFBMFEDMBTT PSUSBJU Λ͍·͠ΐ͏ 4DBMBͷ
ຊ
͜ΕԿʁ
5ZQF4DSJQUͷ4USJOH-JUFSBM5ZQFTΛͬͨ ঢ়ଶͷཧ$IBUXPSL$SFBUPST/PUF ͷվม൛Ͱ͢ɻ
ԿΛղܾ͍ͨ͠ͷ͔
ෳࡶͳঢ়ଶɺ݅ذΛ༰ қʹѻ͑ΔΑ͏ʹ͍ͨ͠ ʢෳࡶͳঢ়ଶɿCPPMFBOͭͱ͔Ͱཧͯ͠ΔΑ͏ͳͷʣ
۩ମతʹ
// ਏ͍ྫ class Message { constructor( private sending: boolean, private
editing: boolean, private deleted: boolean, ) {} canEdit(): boolean { if ( !this.sending && !this.deleted ) { return false; } return true; } }
͞Βʹ
// ͬͱਏ͍ྫ class Message { constructor( private sending: boolean, private
loading: boolean, // New! private editing: boolean, private deleted: boolean, ) {} canEdit(): boolean { if ( !this.sending && !this.loading && // New! !this.deleted ) { return false; } return true; } }
Կ͕ͳͷ͔
w ঢ়ଶͷΈ߹Θ͕ͤ݅ذΛΈͳ͍ͱஅͰ͖ͳ͍ w ݅ذͷJG͕ෳࡶ w ม͕Ճ͞Εͨ߹ͷཏੑͷอূ͕͍͠
// ਏ͍ྫ class Message { constructor( private sending: boolean, private
loading: boolean, // New! private editing: boolean, private deleted: boolean, ) {} canEdit(): boolean { if ( !this.sending && !this.loading && // New! !this.deleted ) { return false; } return true; } }
DMBTTNFUIPEͰ݅ذΛߦ͏ͱɺʮಛఆͷঢ় گԼͰDMBTT͕Ͳ͏͍͏ঢ়ଶʹͳΔ͔ʁʯͷஅ͕͘͠ ͳΔɻ ݅ذͦͷঢ়ଶʹ໊લΛ͚ͭͳ͍ͨΊɺʮ݅ذ ʹҰக͢ΔͷͲ͏͍͏ঢ়ଶ͔ʁʯʹର͢Δղऍ͕ᐆດ ʹͳΔɻ ʢ͜ͷ߹ɺDBO&EJUʮฤूՄೳͳঢ়ଶʯͰҰக͢Δ ͱࢥ͏͚Ͳʣ
// ֎෦͔Βݟͯಉ͡ঢ়ଶ͔ʁ const message = new Message(/.../); const getEditableMessage =
() => { return message.canEdit() ? "मਖ਼Ͱ͖ΔΑʂ" : "मਖ਼ Ͱ͖ͳ͍Αʂ"; } const showEditableIcon = () => { return message.canEdit(); }
ྫ͑ɺ৽͘͠ʮJT4FDSFUʯͱ͍͏มΛՃ͠ ͨ߹ɺDBO&EJUʹӨڹ͢ΔͩΖ͏͔ʁ ·ͨɺӨڹ͢Δͱͯ͠ɺDBO&EJUΛݺͼग़͍ͯ͠ Δଆಉ͡ఆͩΖ͏͔ʁ
Ͳ͏ղܾ͢Δͷ͔
ঢ়ଶΛDMBTTԽ͢Δ
// ָͳྫ type MessageStateLiterals = "sending" | "loading" | "editing"
| "deleted"; class MessageState { private state: MessageStateLiterals; constructor( sending: boolean, loading: boolean, editing: boolean, deleted: boolean, ) { // ݅ʹԠͯ͡this.stateMessageStateLiteralsͷ͍ͣΕ ͔Λೖ } match<R>(matcher: { [key in MessageStateLiterals]: () => R }): R { return matcher[this.status](); } }
͍ํ
class Message { private state: MessageState; constructor( private sending: boolean,
private loading: boolean, private editing: boolean, private deleted: boolean, ) { this.state = new MessageState(true, false, false, false); } canEdit(): boolean { return this.state.match({ "sending": () => false, "loading": () => false, "editing": () => true, "deleted": () => false, }); } }
ར
ར w ঢ়ଶͷஅʹؔΘΔϩδοΫ͕ू͞ΕΔ w ঢ়ଶͷՃ࣌ʹطଘͷ࣮ʹରͯ͠ཏੑΛڧ੍Ͱ͖Δ w ঢ়ଶʹର໊ͯ͠લ͕ͭ͘
ϩδοΫ͕ू͞ΕΔ // ਏ͍ྫ class HogeEntity { private state: HogeState; constructor(
private id: HogeId, private name: string, private enable: boolean, private body: string, private selected: boolean, private focused: boolean, ) {} toHoge() { // ঢ়ଶͷมߋ͕෦ʹӅṭ͞Ε͓ͯΓɺςετ͕͍͠ } }
ϩδοΫ͕ू͞ΕΔ // ָͳྫ type MessageStateLiterals = "sending" | "loading" /…/;
class MessageState { private state: MessageStateLiterals; constructor( sending: boolean, loading: boolean, editing: boolean, deleted: boolean, ) { // ݅ʹԠͯ͡this.stateMessageStateLiteralsͷ͍ͣΕ͔Λೖ } match<R>(matcher: { [key in MessageStateLiterals]: () => R }): R { return matcher[this.status](); } }
ϩδοΫ͕ू͞ΕΔ // ָͳྫʢςετʣ describe(`MessageState`, () => { const matcher =
{ "sending": () => "sending", // ... }; [ { enable: false, selected: false, focused: false, result: "disabled" }, // શͯͷมͷΈ߹ΘͤΛॻ͘ ].forEach(param => { it(JSON.stringify(param), () => { let state = new MessageState(param.enable, param.selected, param.focused); assert(state.match(matcher) === param.result); }); }); });
ঢ়ଶͷՃ࣌ʹطଘͷ࣮ʹରͯ͠ ཏੑΛڧ੍Ͱ͖Δ // matcherʹશͯͷύλʔϯͷkeyΛίϯύΠϥϨϕϧͰڧ੍ match<R>(matcher: { [key in TypeLiterals]: ()
=> R }): R { return matcher[this.status](); }
ঢ়ଶͷՃ࣌ʹطଘͷ࣮ʹରͯ͠ ཏੑΛڧ੍Ͱ͖Δ // key͕ෆ͍ͯ͠Δ߹ɺίϯύΠϧ͕௨Βͳ͍ɻ type MessageStateLiterals = "sending" | "loading"
// …; class MessageState { // ... match<R>(matcher: { [key in MessageStateLiterals]: () => R }): R { return matcher[this.status](); } } const state = new MessageState(/.../); state.match({ "sending": () => true, // Compile error! });
ঢ়ଶʹର໊ͯ͠લ͕ͭ͘ // ਏ͍ྫ class Message { constructor( private sending: boolean,
private editing: boolean, private deleted: boolean, ) {} canEdit(): boolean { // canEditʮฤू͕Մೳʯͱ͍͏݁ՌΛฦ͍ͯ͠Δ͚ͩͰɺ // ͦͷ࣌ͷMessageͷঢ়ଶද͍ͯ͠ͳ͍ɻ } }
DBO&EJU͓ͦΒ͘ʮฤूՄೳͳঢ়ଶʯͱݴ͍͍ͬͯ ͕ɺࢀর͢Δม͕૿͍͑ͯ͘ͱʮಛఆͷঢ়ଶͱͲ͏ ͍͏มͷΈ߹Θͤͳͷ͔ʁʯͷѲ͕͘͠ͳͬͯ ͘Δɻ ʮಛఆͷ݅ʹର͢ΔมͷΈ߹Θͤʯ͕ෆ໌ͳঢ়ଶ ͕ଓ͘ͱɺͦͷ͏ͪʮݱঢ়ͷมͷΈ߹ΘͤΛಛఆͷ ݅ͱ͢ΔʯΑ͏ʹͳΓɺ࣮͕༷Խ͢Δɻ ʢ͔͜͠ΕϦϑΝΫλϦϯάʹϦεΫΛ͍ɺ*%& ͷαϙʔτ͕͋ͬͯਖ਼֬ͳमਖ਼͕ࠔʣ ࣄલʹঢ়ଶΛΓग़͠ɺ໌֬ͳ໊લΛ͚ͭΔ͜ͱͰ࣮
ͷ༷ԽΛࢭ͠ɺमਖ਼ͷ༨Λ͢͜ͱ͕Ͱ͖Δɻ
Ͳ͏͍ͬͨ߹ʹ͏ ͷ͔
Ͳ͏͍ͬͨ߹ʹ͏ͷ͔ w 7BMVF0CKFDUQBUUFSOͷҰ෦ͱͯ͠ w ͦΕͧΕͷ݅ʹ໊ؔͯ͠લ͕ͭ͘ ʢϢϏΩλεݴޠʣ
ࠜຊతʹղܾ͍ͨ͠ Կ͔ʁ
ղܾ͍ͨ͠ w ਖ਼͘͠ྨ͢Δ w ਖ਼໋໊͘͢͠Δ
ʮ݅ذͷ݁Ռͱͯ͠ͷྨʯͰͳ͘ɺ༗ݶݸͷ ྨͷҰͭͱͯ͠ॴଐΛׂΓͯΔɻ ʮૹ৴தϑϥάͱฤूࡁΈϑϥά͕Φϯͷϝοηʔδʯ Ͱͳ͘ɺʮ্ॻ͖ૹ৴தϝοηʔδʯͱͯ͠ڍಈΛׂ ΓͯΔɻ ʮ݅ذͷ݁ՌͷҰ෦ʯͰͳ͘ɺذͷ݁Ռʹର ໊ͯ͠લΛ͚ͭΔɻ ʮૹ৴ऀ͕ۭͰຊจ͕͋Δ߹݅ذ͢ΔʯͷͰͳ ͘ɺʮૹ৴ऀ͕ۭͰຊจ͕͋Δ߹γεςϜϝοηʔδ ͱ໋໊ͯ͢͠Δʯ͜ͱͰɺҎޙʮγεςϜϝοηʔ
δʯʹରͯ͠ॲཧΛߦ͏
࣮ͷ؆ུԽ
ҎԼͷΑ͏ͳDMBTTΛఆٛ͢Δ͜ͱ Ͱ࣮Λ؆ུԽͰ͖Δ type BaseTypeLiterals<L extends string, R> = { [key
in L]: () => R } abstract class BaseType<TypeLiterals extends string> { constructor(protected value: TypeLiterals) {} getValue(): TypeLiterals { return this.value; } equals(type: TypeLiterals): boolean { return this.getValue() === type; } equalType<T extends this>(target: T): boolean { return this.getValue() === target.getValue(); } match<R>(matcher: BaseTypeLiterals<TypeLiterals, R>): R { return matcher[this.getValue()](); } }
ҎԼͷΑ͏ͳDMBTTΛఆٛ͢Δ͜ͱ Ͱ࣮Λ؆ུԽͰ͖Δ class MessageState extends BaseType<"sending" | /.../ > {
constructor( sending: boolean, loading: boolean, /.../ ) { if (sending) { return super("sending"); } // ඞཁͳذΛՃ } }
2"
2" w 2ɿ༷ύλʔϯʢ4QFDJpDBUJPOύλʔϯʣͰʁ w "ɿ:FTɻͨͩɺ5ZQF4DSJQUͰTFBMFEम০ࢠΛ࠶ݱͰ͖ͨͷίʔυΛॻ্͘Ͱศ རͩͬͨ ʢ4DBMBͬͯΔਓʹͨΓલͰʣ w 2ɿ+BWBͷ&OVNͰʁ w
"ɿ+BWBͷ&OVNͰTXJDIͷཏੑΛڧ੍Ͱ͖ͳ͍ͷͰेͰͳ͍ͱ͍͏ཧղ ʢͪͳΈʹ5ZQF4DSJQUͷFOVNશ͘ʹཱͨͳ͍ͱ͍͏ͷਃ͠ఴ͓͖͑ͯ·͢ʣ w 2ɿ4XJGUͷ&OVNͰʁ w "ɿ:FTɻ<4XJGU8BTNDPNQJMF4XJGUUP8FC"TTFNCMZ> IUUQT TXJGUXBTNPSH
͓ΘΓ