Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScriptでType Match的なことをする話 #すえなみチャンス暑気払い

C2dcf3e2f7861b2ec5d84045273ed5bd?s=47 kyo_ago
August 04, 2019

TypeScriptでType Match的なことをする話 #すえなみチャンス暑気払い

C2dcf3e2f7861b2ec5d84045273ed5bd?s=128

kyo_ago

August 04, 2019
Tweet

Transcript

  1. 5ZQF4DSJQUͰ5ZQF.BUDIత ͳ͜ͱΛ͢Δ࿩ ͑͢ͳΈνϟϯεॵؾ෷͍ !LZP@BHP

  2. ݁࿦

  3. TFBMFEDMBTT PSUSBJU Λ࢖͍·͠ΐ͏ 4DBMBͷ

  4. ຊ୊

  5. ͜Ε͸Կʁ

  6. 5ZQF4DSJQUͷ4USJOH-JUFSBM5ZQFTΛ࢖ͬͨ ঢ়ଶͷ؅ཧ$IBUXPSL$SFBUPST/PUF ͷվม൛Ͱ͢ɻ

  7. ԿΛղܾ͍ͨ͠ͷ͔

  8. ෳࡶͳঢ়ଶɺ৚݅෼ذΛ༰ қʹѻ͑ΔΑ͏ʹ͍ͨ͠ ʢෳࡶͳঢ়ଶɿCPPMFBOͭͱ͔Ͱ؅ཧͯ͠ΔΑ͏ͳͷʣ

  9. ۩ମతʹ͸

  10. // ਏ͍ྫ class Message { constructor( private sending: boolean, private

    editing: boolean, private deleted: boolean, ) {} canEdit(): boolean { if ( !this.sending && !this.deleted ) { return false; } return true; } }
  11. ͞Βʹ

  12. // ΋ͬͱਏ͍ྫ 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; } }
  13. Կ͕໰୊ͳͷ͔

  14. ໰୊఺ w ঢ়ଶͷ૊Έ߹Θ͕ͤ৚݅෼ذΛΈͳ͍ͱ൑அͰ͖ͳ͍ w ৚݅෼ذͷJG͕ෳࡶ w ม਺͕௥Ճ͞Εͨ৔߹ͷ໢ཏੑͷอূ͕೉͍͠

  15. // ਏ͍ྫ 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; } }
  16. DMBTTNFUIPE಺Ͱ௚઀৚݅෼ذΛߦ͏ͱɺʮಛఆͷঢ় گԼͰDMBTT͕Ͳ͏͍͏ঢ়ଶʹͳΔ͔ʁʯͷ൑அ͕೉͘͠ ͳΔɻ ৚݅෼ذ͸ͦͷঢ়ଶʹ໊લΛ͚ͭͳ͍ͨΊɺʮ৚݅෼ذ ʹҰக͢Δͷ͸Ͳ͏͍͏ঢ়ଶ͔ʁʯʹର͢Δղऍ͕ᐆດ ʹͳΔɻ ʢ͜ͷ৔߹ɺDBO&EJU͸ʮฤूՄೳͳঢ়ଶʯͰҰக͢Δ ͱࢥ͏͚Ͳʣ

  17. // ֎෦͔Βݟͯಉ͡ঢ়ଶ͔ʁ const message = new Message(/.../); const getEditableMessage =

    () => { return message.canEdit() ? "मਖ਼Ͱ͖ΔΑʂ" : "मਖ਼ Ͱ͖ͳ͍Αʂ"; } const showEditableIcon = () => { return message.canEdit(); }
  18. ྫ͑͹ɺ৽͘͠ʮJT4FDSFUʯͱ͍͏ม਺Λ௥Ճ͠ ͨ৔߹ɺDBO&EJUʹӨڹ͢ΔͩΖ͏͔ʁ ·ͨɺӨڹ͢Δͱͯ͠ɺDBO&EJUΛݺͼग़͍ͯ͠ Δଆ΋ಉ͡૝ఆͩΖ͏͔ʁ

  19. Ͳ͏ղܾ͢Δͷ͔

  20. ঢ়ଶΛDMBTTԽ͢Δ

  21. // ָͳྫ type MessageStateLiterals = "sending" | "loading" | "editing"

    | "deleted"; class MessageState { private state: MessageStateLiterals; constructor( sending: boolean, loading: boolean, editing: boolean, deleted: boolean, ) { // ৚݅ʹԠͯ͡this.state΁MessageStateLiteralsͷ͍ͣΕ ͔Λ୅ೖ } match<R>(matcher: { [key in MessageStateLiterals]: () => R }): R { return matcher[this.status](); } }
  22. ࢖͍ํ

  23. 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, }); } }
  24. ར఺

  25. ར఺ w ঢ়ଶͷ൑அʹؔΘΔϩδοΫ͕ू໿͞ΕΔ w ঢ়ଶͷ௥Ճ࣌ʹطଘͷ࣮૷ʹରͯ͠໢ཏੑΛڧ੍Ͱ͖Δ w ঢ়ଶʹର໊ͯ͠લ͕ͭ͘

  26. ϩδοΫ͕ू໿͞ΕΔ // ਏ͍ྫ 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() { // ঢ়ଶͷมߋ͕಺෦ʹӅṭ͞Ε͓ͯΓɺςετ͕೉͍͠ } }
  27. ϩδοΫ͕ू໿͞ΕΔ // ָͳྫ type MessageStateLiterals = "sending" | "loading" /…/;

    class MessageState { private state: MessageStateLiterals; constructor( sending: boolean, loading: boolean, editing: boolean, deleted: boolean, ) { // ৚݅ʹԠͯ͡this.state΁MessageStateLiteralsͷ͍ͣΕ͔Λ୅ೖ } match<R>(matcher: { [key in MessageStateLiterals]: () => R }): R { return matcher[this.status](); } }
  28. ϩδοΫ͕ू໿͞ΕΔ // ָͳྫʢςετʣ 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); }); }); });
  29. ঢ়ଶͷ௥Ճ࣌ʹطଘͷ࣮૷ʹରͯ͠ ໢ཏੑΛڧ੍Ͱ͖Δ // matcherʹશͯͷύλʔϯͷkeyΛίϯύΠϥϨϕϧͰڧ੍ match<R>(matcher: { [key in TypeLiterals]: ()

    => R }): R { return matcher[this.status](); }
  30. ঢ়ଶͷ௥Ճ࣌ʹطଘͷ࣮૷ʹରͯ͠ ໢ཏੑΛڧ੍Ͱ͖Δ // 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! });
  31. ঢ়ଶʹର໊ͯ͠લ͕ͭ͘ // ਏ͍ྫ class Message { constructor( private sending: boolean,

    private editing: boolean, private deleted: boolean, ) {} canEdit(): boolean { // canEdit͸ʮฤू͕Մೳʯͱ͍͏݁ՌΛฦ͍ͯ͠Δ͚ͩͰɺ // ͦͷ࣌ͷMessageͷঢ়ଶ͸ද͍ͯ͠ͳ͍ɻ } }
  32. DBO&EJU͸͓ͦΒ͘ʮฤूՄೳͳঢ়ଶʯͱݴ͍͍ͬͯ ͕ɺࢀর͢Δม਺͕૿͍͑ͯ͘ͱʮಛఆͷঢ়ଶͱ͸Ͳ͏ ͍͏ม਺ͷ૊Έ߹Θͤͳͷ͔ʁʯͷ೺Ѳ͕೉͘͠ͳͬͯ ͘Δɻ ʮಛఆͷ৚݅ʹର͢Δม਺ͷ૊Έ߹Θͤʯ͕ෆ໌ͳঢ়ଶ ͕ଓ͘ͱɺͦͷ͏ͪʮݱঢ়ͷม਺ͷ૊Έ߹ΘͤΛಛఆͷ ৚݅ͱ͢ΔʯΑ͏ʹͳΓɺ࣮૷͕࢓༷Խ͢Δɻ
 ʢ͔͠΋͜Ε͸ϦϑΝΫλϦϯάʹϦεΫΛ൐͍ɺ*%& ౳ͷαϙʔτ͕͋ͬͯ΋ਖ਼֬ͳमਖ਼͕ࠔ೉ʣ ࣄલʹঢ়ଶΛ੾Γग़͠ɺ໌֬ͳ໊લΛ͚ͭΔ͜ͱͰ࣮૷

    ͷ࢓༷ԽΛ཈ࢭ͠ɺमਖ਼ͷ༨஍Λ࢒͢͜ͱ͕Ͱ͖Δɻ
  33. Ͳ͏͍ͬͨ৔߹ʹ࢖͏ ͷ͔

  34. Ͳ͏͍ͬͨ৔߹ʹ࢖͏ͷ͔ w 7BMVF0CKFDUQBUUFSOͷҰ෦ͱͯ͠ w ͦΕͧΕͷ৚݅ʹ໊ؔͯ͠લ͕ͭ͘
 ʢϢϏΩλεݴޠʣ

  35. ࠜຊతʹղܾ͍ͨ͠໰ ୊͸Կ͔ʁ

  36. ղܾ͍ͨ͠໰୊ w ਖ਼͘͠෼ྨ͢Δ w ਖ਼໋໊͘͢͠Δ

  37. ʮ৚݅෼ذͷ݁Ռͱͯ͠ͷ෼ྨʯͰ͸ͳ͘ɺ༗ݶݸͷ෼ ྨͷҰͭͱͯ͠ॴଐΛׂΓ౰ͯΔɻ ʮૹ৴தϑϥάͱฤूࡁΈϑϥά͕Φϯͷϝοηʔδʯ Ͱ͸ͳ͘ɺʮ্ॻ͖ૹ৴தϝοηʔδʯͱͯ͠ڍಈΛׂ Γ౰ͯΔɻ ʮ৚݅෼ذͷ݁ՌͷҰ෦෼ʯͰ͸ͳ͘ɺ෼ذͷ݁Ռʹର ໊ͯ͠લΛ͚ͭΔɻ ʮૹ৴ऀ͕ۭͰຊจ͕͋Δ৔߹৚݅෼ذ͢ΔʯͷͰ͸ͳ ͘ɺʮૹ৴ऀ͕ۭͰຊจ͕͋Δ৔߹γεςϜϝοηʔδ ͱ໋໊ͯ͢͠Δʯ͜ͱͰɺҎޙ͸ʮγεςϜϝοηʔ

    δʯʹରͯ͠ॲཧΛߦ͏
  38. ࣮૷ͷ؆ུԽ

  39. ҎԼͷΑ͏ͳ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()](); } }
  40. ҎԼͷΑ͏ͳDMBTTΛఆٛ͢Δ͜ͱ Ͱ࣮૷Λ؆ུԽͰ͖Δ class MessageState extends BaseType<"sending" | /.../ > {

    constructor( sending: boolean, loading: boolean, /.../ ) { if (sending) { return super("sending"); } // ඞཁͳ෼ذΛ௥Ճ } }
  41. 2"

  42. 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
  43. ͓ΘΓ