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

Functional TypeScript

Naoya Ito
September 08, 2024

Functional TypeScript

オープンデベロッパーズカンファレンス(ODC)2024 での発表資料

Naoya Ito

September 08, 2024
Tweet

More Decks by Naoya Ito

Other Decks in Technology

Transcript

  1. ઌʹ૯࿦ • ؔ਺ܕϓϩάϥϛϯάͷΤοηϯεΛऔΓೖΕΔ͜ͱͰΑΓྑ͍ϓϩάϥϛϯά͕Ͱ͖Δݴޠ – 3FBDUͳͲ͕ྑ͍ྫ – ߴػೳͳܕγεςϜɺؔ਺Ϧςϥϧɺߴ֊ؔ਺ɺΫϩʔδϟɺNBQSFEVDFɺ෼ׂ୅ೖͳͲͷγϯλοΫεαϙʔτ – ୅਺తσʔλܕ΋ΤϛϡϨʔτ͸Ͱ͖Δ •

    )BTLFMM΍ 4DBMBͷΑ͏ͳؔ਺ܕϓϩάϥϛϯάݴޠʹൺֱ͢Δͱػೳ͸ෆ଍͍ͯ͠Δ – ͦΕ͕ࠓޙɺվྑ͞Ε͍ͯ͘ϩʔυϚοϓ͸ߟ͑ʹ͍͘ • ྫ֎΍ΤϥʔΛؚΉ෭࡞༻ΛͲ͏ѻ͏͔ɺӬଓσʔλΛͲ͏ѻ͏͔͸ݴޠ͕ࢦࣔͯ͘͠Εͳ͍ – )BTLFMM΍ 4DBMBɺ'ͳͲͱಉ༷ͷΞϓϩʔνΛͱΖ͏ʹ΋ɺݴޠͷαϙʔτػߏ͕ෆे෼ – αʔυύʔςΟϥΠϒϥϦͰʮิ͏ʯఔ౓͕ݶ౓͔
  2. ͦ΋ͦ΋ʮؔ਺ܕϓϩάϥϛϯάʯͬͯ • ໋ྩܕ ؔ਺ܕ – จ ໭Γ஋Λ൐Θͳ͍ ͰܭࢉΛߏ੒͢Δ uuu ໋ྩܕ

    – ࣜ ໭Γ஋Λ൐͏ ͰܭࢉΛએݴ͢Δ uuu ؔ਺ܕ • ؔ਺ܕϓϩάϥϛϯάͬΆ͍ΠσΟΦϜ – NBQSFEVDF – ෦෼ద༻ɺΧϦʔԽ • ܕγεςϜ – ୅਺తσʔλܕͱύλʔϯϚον – จ຺෇͖ܭࢉɺϞφυɺܕΫϥεɺߴ֊ଟ૬uuu • Πϛϡʔλϒϧ ϛϡʔλϒϧ – Ӭଓσʔλ
  3. ໋ྩܕGPSจͰॻ͘ int total = 0; for (int i = 1;

    i <= n; i++) { total += i; } • GPSจͱ୅ೖจͰɺ܁Γฦ͠ԋࢉ݁ՌΛॻ͖ࠐΉ ୅ೖ͢Δ ໋ྩΛߦ͍ͬͯΔ • ܭࢉػ΁ͷ໋ྩ uuu໋ྩܕ • จͰܭࢉΛߏ੒͢Δͱɺ໋ྩతʹͳΔ
  4. ؔ਺ܕ࠶ؼతʹؔ਺Λద༻͢Δ • ࣜʹΑΓܭࢉΛએݴ͢Δ • ࣜ͸ඞͣ஋Λ໭͢ɻͦͷ஋ʹ࠶ͼؔ਺Λద༻͢Δ • ࣜͰܭࢉΛߏ੒͢ΔͱɺએݴతʹͳΔ let s =

    foldl (+) 0 [1 .. n] )BTLFMM let s = [1, 2, 3, 4, 5].reduce((acc, i) => acc + i, 0)``` let s = [1, 2, 3, 4, 5].reduce((acc, i) => acc + i, 0) 5ZQF4DSJQU
  5. customer.archive() 5ZQF4DSJQU const archived = archiveCustomer(customer) 5ZQF4DSJQU • ໋ྩతʹॻ͘ –

    ΦϒδΣΫτͷ಺෦ঢ়ଶΛॻ͖׵͑Δ໋ྩΛߦ͏͜ͱͰɺঢ়ଶΛมԽͤ͞Δ – ঢ়ଶͷมԽ͕҉໧త • ؔ਺త એݴత ʹॻ͘ – Ҿ਺ͷΦϒδΣΫτʹؔ਺Λద༻ͯ͠ɺঢ়ଶભҠޙͷΦϒδΣΫτΛಘΔ – ભҠલͷঢ়ଶ͸ඞͣҾ਺ʹݱΕɺભҠޙͷঢ়ଶ͸໭Γ஋ʹݱΕΔ
  6. એݴతʹؔ਺Λهड़͢Δͷ͸΍Γ΍͍͢ݴޠ • ୈҰڃؔ਺ɺߴ֊ؔ਺ɺΫϩʔδϟ • ؔ਺Ϧςϥϧ • ෼ׂ୅ೖ • NBQSFEVDF •

    ੩తܕ෇͚ɺܕਪ࿦ ʮࣜͰܭࢉΛએݴతʹఆٛ͢Δʯͱ͍͏఺ʹ͓͍ͯ͸े෼ͳػೳΛ༗͢Δ ෼ׂ୅ೖͷγϯλοΫεͰΠϛϡʔλϒϧͳؔ਺΋هड़͠΍͍͢
  7. ΧϦʔԽ͸ݴޠͷػೳͱͯ͠͸ͳ͍͕ɺࣗ෼Ͱهड़͢Ε͹ྑ͍ • ΫϩʔδϟΛฦؔ͢਺ͱͯ͠ఆٛ͢Ε͹Α͍ • ෦෼ద༻ʹΑΔ %FQFOEFODZ*OKFDUJPO ͳͲʹ΋ॏཁ export type getTagSortOrder

    = ({ groupId }: { groupId: RestaurantGroupId }) => ResultAsync<number, PrismaClientError> export const getTagSortOrder = ({ prisma }: applicationContext): getTagSortOrder => ({ groupId }) => ResultAsync.fromPromise( prisma.tag .aggregate({ _max: { sortOrder: true, }, where: { groupId }, }) .then((x) => (x._max.sortOrder ? x._max.sortOrder + 1 : 1)), PrismaClientError )
  8. 5ZQF4DSJQUͷܕγεςϜ • ߏ଄త෦෼ܕ TUSVDUVSBMTVCZQF ͰߴػೳͳܕγεςϜɾܕਪ࿦ – Ϧςϥϧܕ – δΣωϦΫε –

    ߹ซܕ 6OJPO ަࠩܕ *OUFSTFDUJPO – ಈతͳܕఆٛ uuu $POEJUJPJOBM5ZQFTɺ6UJMJUZ5ZQFTɺ.BQQFE5ZQFTɺJOGFS • ҎԼ͸ɺΤϛϡϨʔτͰ͖Δ – ୅਺తσʔλܕͱύλʔϯϚον • Ͱ͸͋Δ͕ɺҎԼ͸ͳ͍ – 0QUJPOBM΍ 3FTVMUͳͲΛந৅తʹѻ͏࢓૊Έ uuu Ϟφυ΍ܕΫϥεɺߴ֊ଟ૬ FUD
  9. ୅਺తσʔλܕ uuu ੩తܕ෇͚ͷؔ਺ܕݴޠͰσʔλߏ଄Λఆٛ͢Δखஈ • )BTLFMMͳͲͷݴޠͰ͸σʔλߏ଄Λఆٛ͢Δͷʹ୅਺తσʔλܕͰදݱ͢Δ • ܕΛ૊Έ߹ΘͤΔͷʹੵ "/% ͚ͩͰͳ͘ ࿨

    03 ͕࢖͑Δ data Bool = True | False data Maybe a = Nothing | Just a )BTLFMM )BTLFMM data UnionFind = UnionFind { parent :: IM.IntMap Int, size :: IM.IntMap Int } )BTLFMM
  10. ࿨Ͱ૊Έ߹Θͤͯߏஙͨ͠΋ͷ͸ɺύλʔϯϚονͰ෼ղ main = do let someValue :: Maybe String someValue

    = ... case someValue of Just s -> putStrLn (s ++ ", naoya") Nothing -> putStrLn "Farewell" data Maybe a = Nothing | Just a )BTLFMM )BTLFMM +VTU4USJOH͔ /PUIJOHͷͲͪΒ͔ .BZCFܕΛύλʔϯϚονͰ෼ղ͢Δ
  11. 5ZQF4DSJQU͸૊ΈࠐΈͰ͸୅਺తσʔλܕΛαϙʔτͯ͠ͳ͍͕ɺΤϛϡϨʔτͰ͖Δ interface Empty { kind: "Empty" } interface Cons<T> {

    kind: "Cons" head: T tail: List<T> } export type List<T> = Empty | Cons<T> data List a = Empty | Cons a (List a) 5ZQF4DSJQU )BTLFMM ϦςϥϧܕͰλάΛ͚͓ͭͯ͘ ϢχΦϯͰ૊Έ߹Θͤͨܕ
  12. // List<T> への map 関数を実装 type map = <T, U>(f:

    (a: T) => U, xs: List<T>) => List<U> export const map: map = (f, xs) => { switch (xs.kind) { case "Empty": return Empty() case "Cons": return Cons(f(xs.head), map(f, xs.tail)) default: assertNever(xs) } } export function assertNever(_: never): never { throw new Error() } console.log(map(i => i * 2, myList)) 5ZQF4DSJQU λάʹԠͯ͡෼ղ ϦςϥϧܕͳͷͰͪΌΜͱܕ͕ޮ͘ ͜ΕΛೖΕΔ͜ͱͰ໢ཏੑνΣοΫ͕ޮ͘
  13. export function toColor(value: string): Result<Color, ValidationError> { return /^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(value) ?

    ok(value as Color) : err(new ValidationError('⾊の値が不正です。#FFFFFF形式で指定してください')) } 5ZQF4DSJQU 3FTVMUܕ uuu੒ޭ ࣦഊͷ෼ذΛܕͰදݱͰ͖Δ OFWFSUISPXɺGQUTɺ&GGFDUͳͲ͕ಉ༷ͷܕΛϥΠϒϥϦͰఏڙ͢Δ
  14. 3FTVMUܕͰɺࣦഊͷՄೳੑͷ͋ΔܭࢉΛҰຊಓʹ߹੒͢Δ͜ͱ͕Ͱ͖Δ import { Result, ok, err } from 'neverthrow' function

    itsUnder100(n: number): Result<number, Error> { return n <= 100 ? ok(n) : err(new Error('100より大きい数字です')) } function itsEven(n: number): Result<number, Error> { return n % 2 == 0 ? ok(n) : err(new Error('奇数です')) } function itsPositive(n: number): Result<number, Error> { return n > 0 ? ok(n) : err(new Error('負数です')) } const result = ok(96).andThen(itsUnder100).andThen(itsEven).andThen(itsPositive) result.match( (n) => console.log(n), (error) => { throw error } )
  15. ͔͠͠ɺ3FTVMUܕʹೖͬͨ஋͕ೖΕࢠʹͳΔͱ΍΍͍͜͠ export const Tag = (input: TagInput): Result<Tag, ValidationError> =>

    { const tagId = TagId(input.id) const groupId = RestaurantGroupId(input.groupId) const label = TagLabel(input.label) const icon = input.icon && input.iconType ? TagIcon({ symbol: input.icon, type: input.iconType, color: input.color, }) : ok(NoIcon()) const sortOrder = FractionalIndex(input.sortOrder) const values = Result.combine(tuple(tagId, groupId, label, icon, sortOrder)) return values.map(([id, groupId, label, icon, sortOrder]) => ({ ...input, id, groupId, label, icon, sortOrder, })) } ஋͕͍͍ͩͨ3FTVMUʹೖ͍ͬͯΔ ͨΊɺෳ਺3FTVMU͕͋Δͱ߹੒͠ ͯϑϥοτʹ͢Δඞཁ͕͋Γ໘౗ ͳ࡞ۀʹͳͬͯ͘Δ
  16. ʮίϯςφʹೖͬͨจ຺͖ͭͷ஋ʯΛѻ͏ɺ૊ΈࠐΈͷػೳ͕͋ΔݴޠͳΒuuu ͜ͷखͷػߏ͕͋Ε͹ೝ஌ෛՙ௿࣮͘૷Ͱ͖Δ͕ɺ࢒೦ͳ͕Β 5ZQF4DSJQUʹ͸ͳ͍ GQUT΍ &GGFDU͕ϑϥοτ߹੒ͷؔ਺ QJQF Λఏڙͯ͠͸͍Δ͕ɺॻ͖ຯ͸ྑ͘ͳ͍ makeTagId :: String

    -> Either ValidationError TagId makeTagId tagId | null tagId = Left (ValidationError "tagId is empty") | otherwise = Right (TagId tagId) makeTag :: String -> String -> Int -> Either ValidationError Tag makeTag id gid order = do tagId <- makeTagId id groupId <- makeGroupId gid sortOrder <- makeSortOder order return (Tag tagId groupId sortOrder) )BTLFMM ྫ͑͹Ϟφυʹ͸ɺೖΕࢠʹ ͳͬͨίʔυΛฏୱԽ͢Δޮ༻ ͕͋Δ
  17. )BTLFMMͷجຊతͳσʔλߏ଄͸Ӭଓσʔλߏ଄ main :: IO () main = do let xs

    = scanl' (flip Set.insert) Set.empty [1 .. 10 ^ 5] print $ Set.size (last xs) )BTLFMM Ӭଓσʔλߏ଄ͳΒΠϛϡʔλϒϧʹσʔλΛѻ͍ͭͭύϑΥʔϚϯεͱཱ྆Ͱ͖Δ ݸͷ 4FU͕࡞ΒΕΔ͕ͦͷ౎ ౓શମ͕ຖճίϐʔ͞ΕΔΘ͚Ͱ ͸ͳ͍
  18. ࠶ܝ ૯࿦ • ؔ਺ܕϓϩάϥϛϯάͷΤοηϯεΛऔΓೖΕΔ͜ͱͰΑΓྑ͍ϓϩάϥϛϯά͕Ͱ͖Δݴޠ – 3FBDUͳͲ͕ྑ͍ྫ – ߴػೳͳܕγεςϜɺؔ਺Ϧςϥϧɺߴ֊ؔ਺ɺΫϩʔδϟɺNBQSFEVDFɺ෼ׂ୅ೖͳͲͷγϯλοΫεαϙʔτ – ୅਺తσʔλܕ΋ΤϛϡϨʔτ͸Ͱ͖Δ

    • )BTLFMM΍ 4DBMBͷΑ͏ͳؔ਺ܕϓϩάϥϛϯάݴޠʹൺֱ͢Δͱػೳ͸ෆ଍͍ͯ͠Δ – ͦΕ͕ࠓޙɺվྑ͞Ε͍ͯ͘ϩʔυϚοϓ͸ߟ͑ʹ͍͘ • ྫ֎΍ΤϥʔΛؚΉ෭࡞༻ΛͲ͏ѻ͏͔ɺӬଓσʔλΛͲ͏ѻ͏͔͸ݴޠ͕ࢦࣔͯ͘͠Εͳ͍ – )BTLFMM΍ 4DBMBɺ'ͳͲͱಉ༷ͷΞϓϩʔνΛͱΖ͏ʹ΋ɺݴޠͷαϙʔτػߏ͕ෆे෼ – αʔυύʔςΟϥΠϒϥϦͰʮิ͏ʯఔ౓͕ݶ౓͔
  19. ࢲݟ • 5ZQF4DSJQU͸ؔ਺ܕϓϩάϥϛϯάʹಛԽͨ͠ݴޠͰ͸ͳ͍ͨΊɺؔ਺ܕʹدͤ͗͢Δͷ΋ߟ͑΋ͷ – खଓ͖తʹॻ͚͹Α͍ͱ͜Ζ͸ɺखଓ͖Λ࢖͏ – ಛʹ *0΍ඇಉظॲཧपΓ͸ແཧʹؔ਺ܕʹ͢Δඞཁ͸ͳ͍ • )BTLFMMͳͲଞͷؔ਺ܕݴޠͰ΋ɺ࡞༻Λ൐͏ͱ͜Ζ͸खଓ͖తʹ࣮૷͢Δ͜ͱ΋ଟ͍

    – ࢀߟʰ5ZQF4DSJQUͰͲ͜·Ͱʮؔ਺ܕϓϩάϥϛϯάʯ͢Δ͔ ᴷʮखଓ͖ )BTLFMMʯ͔Βߟ࡯͢Δʱ • IUUQTVTFSGJSTUJLZVDPKQFOUSZ • *0෼཭Λੵۃతʹߦͬͯۀ຿ϩδοΫΛ७ਮʹ͠ɺ७ਮؔ਺Ͱߏ੒͢Δͷ͸ྑ͍ϓϥΫςΟεͩͱࢥ͏