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
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
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
410
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
640
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
14
5.9k
Contextとはなにか
chiroruxx
1
380
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
150
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
110
Dataformのリポジトリを立ち上げるときにまずやること / dataform-day0-2026
snhryt
0
190
これからAgentCoreを触る方へトレンドはGatewayです
har1101
2
310
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
270
Oxlintのカスタムルールの現況
syumai
6
1.2k
代数的データ型って何が嬉しいの? #frontend_phpcon_do
kajitack
8
3.8k
AI駆動開発を妨げる技術的負債の解消アプローチ / ai-refactoring-approach
minodriven
15
7.5k
Featured
See All Featured
Large-scale JavaScript Application Architecture
addyosmani
515
110k
So, you think you're a good person
axbom
PRO
2
2.1k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
370
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
Exploring anti-patterns in Rails
aemeredith
3
430
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Automating Front-end Workflow
addyosmani
1370
210k
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
280
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Writing Fast Ruby
sferik
630
63k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
62
55k
The Cost Of JavaScript in 2023
addyosmani
55
10k
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
͓ΘΓ