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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
700
The NotImplementedError Problem in Ruby
koic
1
800
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
200
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
270
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
120
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
250
Datadog × OpenTelemetry 入門と実践のあいだ
kn_to_maxpno
1
160
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
170
AIとASP.NET Coreで雑Webアプリを作った話
mayuki
0
640
エージェンティックRAGにAWSで入門しよう!
har1101
8
1.6k
Oxlintのカスタムルールの現況
syumai
6
1.1k
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
225
10k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
330
Are puppies a ranking factor?
jonoalderson
1
3.6k
Making the Leap to Tech Lead
cromwellryan
135
9.9k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
390
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
The Cult of Friendly URLs
andyhume
79
6.9k
4 Signs Your Business is Dying
shpigford
187
22k
Prompt Engineering for Job Search
mfonobong
0
350
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
240
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.5k
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
͓ΘΓ