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

Functional TypeScript

Avatar for Naoya Ito Naoya Ito
September 08, 2024

Functional TypeScript

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

Avatar for Naoya Ito

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෼཭Λੵۃతʹߦͬͯۀ຿ϩδοΫΛ७ਮʹ͠ɺ७ਮؔ਺Ͱߏ੒͢Δͷ͸ྑ͍ϓϥΫςΟεͩͱࢥ͏