ãã®è¨äºã¯ ä¸ä¼.com Advent Calendar 2024 ã®15æ¥ç®ã®è¨äºã§ãã
äºå®ããæ©ãæ¸ãä¸ãã¦ãã¾ã£ãã®ã§ããã©ã¤ã³ã°ã§ããå
¬éãã¦ãã¾ãã¾ãã
TypeScript ã® Discriminated Union (å¤å¥å¯è½ãª Union å) ã使ãã¨ãããããã代æ°çãã¼ã¿åãã®ã¦ã¼ã¹ã±ã¼ã¹ã模å£ãããã¨ãã§ãã¾ããä¸ä¼ã®ãããªäºç´ã·ã¹ãã éçºã«ããã¦ã¯ãããããªãç¶æ ã表ç¾ããªããæ¹éã§åã宣è¨ããããã«ããå©ç¨ããã¦ãã¾ãã
ãããå¾ãªãç¶æ ã表ç¾ããªããã¨ããå宣è¨ã®æ¹éã«ã¤ãã¦ã¯ä»¥ä¸ã® URL ãåèã«ãªãã¾ãã
Designing with types: Making illegal states unrepresentable | F# for fun and profit
ãã®ã¦ã¼ã¹ã±ã¼ã¹ã§ Discriminated Union ã使ãå ´åãããã¯æåã©ãããåã®å¤å¥ãã®ããã«ä½¿ããã¾ãããã®å ´åãå¤å¥ã®æãããã¨ãªãããã£ã¹ã¯ãªããã¼ã¿ã¼ãã¯ãã ã®åå²ã®ããã®ã·ã³ãã«ç¨åº¦ã®å½¹å²ã«ããè¦ããªãã§ãããããããããã¯ãæ¬æ©è½ã®é¨åçãªè¦æ¹ã§ãããªãã¨èãã¦ãã¾ãã
Haskell ãªã©ãTypeScript ã®ããã«æ¨¡å£ã§ã¯ãªããåã·ã¹ãã ã«ä»£æ°çãã¼ã¿åããã¤ãã£ãã«çµã¿è¾¼ã¾ãã¦ããããã°ã©ãã³ã°è¨èªã§ã¯ã代æ°çãã¼ã¿åã¯æ°ããªãã¼ã¿åã¨ãã¼ã¿æ§é ã宣è¨ããä¸è¬çãªæ段ã§ãã代æ°çãã¼ã¿æ§é ã¨ãã¿ã¼ã³ããããç¨ãã¦ãä¸è¬çãªãªãã¸ã§ã¯ãã ãã§ãªãããªã¹ããæ¨æ§é ãªã©ã®ãã¼ã¿åãæ§ç¯ã»æä½ãããã¨ãã§ãã¾ãããã¡ãã®ã¡ã³ã¿ã«ã¢ãã«ããè¦ãã¨ä»£æ°çãã¼ã¿åãããããã¼ã¿ã®æ§ç¯ã¨å解ãåå®å ¨ãã¤è¡¨ç¾åè±ãã«æ±ãåºç¤ãæä¾ãããã®ã§ãããåé§åéçºãæ¯ããæ ¹å¹¹ã ã¨æãããã¾ãã
æ¬è¨äºã§ã¯ TypeScript ã® Discriminated Union ã«ãã代æ°çãã¼ã¿åã®æ¨¡å£ã«ã¤ãã¦ã¾ããã®åºæ¬ã確èªãããã®å¾ Haskell ã®ä»£æ°çãã¼ã¿åã®ææ³ãã¿ã¦ããã¾ããå¾è ãã¿ã¦å ã®ã¡ã³ã¿ã«ã¢ãã«ãç²å¾ããã®ã¡ã«åè ãæ¹ãã¦çºãã¦ã¿ããã¨ã«ãããæ°ããªè¦ç¹ã§ TypeScript ã®æ©è½ãæãããã¨ãç®æãã¾ãã
TypeScript ã® Discriminated Union (å¤å¥å¯è½ãª Union å)
TypeScript ã® Discriminated Union (å¤å¥å¯è½ãª Union å) ã使ãã¨ãä»ã®ããã°ã©ãã³ã°è¨èªã§ããã¨ããã®ä»£æ°çãã¼ã¿åã®ã¦ã¼ã¹ã±ã¼ã¹ã模å£ãããã¨ãã§ãã¾ããDiscriminated Union ã¯ãã£ã¹ã¯ãªããã¼ã¿ã¼ (ãããã¯ã¿ã°) ã¨å¼ã°ããæååãªãã©ã«ã«ãã Union ã§åä½µããåã«å«ã¾ããåãå¤å¥ã§ããã¨ãããããã¿ã°ã¤ã Union åãã¨å¼ã°ãããã¨ãããã¾ãã
Discriminated Union ããã¾ã使ãã¨ãã¢ããªã±ã¼ã·ã§ã³éçºã«ããã¦ãåå¨ããªãç¶æ ããã§ãããã¨ãåé¿ãããã¨ãåºæ¥ã¾ããåå¨ããç¶æ ã®ã¿ãåã§å®£è¨ãããã¨ã§ãåå¨ããªãç¶æ ãã§ãã¦ããªããã¨ããåãã§ãã¯ã«ããä¿è¨¼ãããã¨ãã§ãã¾ããæ¸ç± Domain Modeling Made Functional ãªã©ã§ãèªããã¦ããé常ã«æç¨ãªå®è£ ãã¿ã¼ã³ã§ãããä¸ä¼ãæ±ãäºç´ãªã©ã®æ¥åã·ã¹ãã éçºã§ãé »ç¹ã«å©ç¨ãã¦ãã¾ãã
å°ããã®æ§åãè¦ã¦ã¿ã¾ãã
å ¸åä¾ã¨ãã¦ãä½ãããã®ã·ã¹ãã ã®ã¦ã¼ã¶ã¼ (User) ã«ã¤ãã¦èãã¾ããã¦ã¼ã¶ã¼ã«ã¯ä¼å¡ç»é²æ¸ã¿ã®ä¼å¡ (Member) ã¨ãä¼å¡ç»é²ã¯ãã¦ããªãã²ã¹ãä¼å¡ (Guest) ã®åºåãããã¨ããã®ã¯ãããããã±ã¼ã¹ã§ããããä¼å¡ã¯ã¦ã¼ã¶ã¼IDãååãã¡ã¼ã«ã¢ãã¬ã¹ãªã©ã®å¤ããã¤ããã²ã¹ãã¯ãããã確å®ãã¦ããªãã
ãã®ã¨ã ã¦ã¼ã¶ã¼ID ã null ãªãã¼ã¿ãã²ã¹ãã¦ã¼ã¶ã¼ã¨ãã¦æ±ãã¨ããå®è£ ãããå¾ã¾ãããnull ãã§ãã¯ãå¿ è¦ã«ãªãããID ã null ãªã®ãã²ã¹ããã¨ããæé»ã®ä»æ§ãæã¡è¾¼ããã¨ã«ãªã£ã¦ãã¾ãã¾ããnull ã«æå³ã¯ä¸ãããããã¾ããã
ããã§ä»¥ä¸ã®ããã«ãMember 㨠Guest ãå®ç¾©ãã¾ãã
type User = Member | Guest type Member = { kind: "Member" id: number name: string email: string } type Guest = { kind: "Guest" }
User åã®ãªãã¸ã§ã¯ãããã£ãã¨ãããã®ãªãã¸ã§ã¯ãã Member åãªã®ã Guest åãªã®ã㯠kind
ããããã£ã®å¤ã«ãã£ã¦å¤å¥ã§ãã¾ãããã® kind
ããããã£ãåã®å¤å¥ã«ä½¿ããããã£ã¹ã¯ãªããã¼ã¿ã¼ (ãããã¯ã¿ã°) ã§ãã
ä¾ãã°ãMember ã Guest ãã§ãã¬ã¼ã³ãã¼ã·ã§ã³ãåãããã¨ããã¨ãã¯ä»¥ä¸ã®ããã« switch æã«ãã Union åãå解ããããããã®åãã¨ã«å¦çãè¨è¿°ãããã¨ãã§ãã¾ãã
function showUser(user: User): string { switch (user.kind) { case "Member": return `ID: ${user.id}, Name: ${user.name}, Email: ${user.email}` case "Guest": return "Guest" default: assertNever(user) } } export function assertNever(_: never): never { throw new Error("Unexpected value. Should have been never.") }
assertNever
ã¯ç¶²ç¾
æ§ãã§ãã¯ã®ããã®ã¤ãã£ãªã ã§ããããç½®ããã¨ã§ããã¼ã¤ã³ã°ã®çµæ User åã«å«ã¾ãããã¹ã¦ã®åã«å¯¾ãå¦çãå®ç¾©ãããããã³ã³ãã¤ã«æã«ãã§ãã¯ãããã¨ãã§ãã¾ãã
以ä¸ã®çµµã¯å®è£
éä¸ã® VSCode ã§ããMember
ã«å¯¾ããå¦çã¯è¨è¿°ããã Guest
ã«å¯¾ããå¦çã¯ã¾ã è¨è¿°ãã¦ããªã段éãã³ã³ãã¤ã©ãã¨ã©ã¼ãåºãã¦ããã¦ãã¾ãã
ãã㦠kind
ããããã£ããªãã¡ãã£ã¹ã¯ãªããã¼ã¿ã¼ã¯ãªãã©ã«åã«ãªã£ã¦ãããè£å®ãå¹ãã¾ãã
ãã®ããã«ãUnion ã«ããæ§é ã®ç°ãªãè¤æ°ã®åãåä½µãã¤ã¤ããã£ã¹ã¯ãªããã¼ã¿ã¼ã«ãã£ã¦ãããå解ãããã¨ãã§ããããã¼ã¤ã³ã°ã«ãã£ã¦åãç¶²ç¾ æ§ãã§ãã¯ãå¹ããã¨ããã代æ°çãã¼ã¿åãã¨ãã¥ã¼ã¬ãã§ãã¦ããã¨è¨ããã¾ãããã£ã¹ã¯ãªããã¼ã¿ã¼ã«åºã¥ãã switch æã§ã®åã®å解ã¯ãããªããããã¿ã¼ã³ããããã®ããã«æãããã¾ãã
仮㫠Discriminated Union ã使ãããã²ã¹ãã¦ã¼ã¶ã¼ããID ã nullãã§è¡¨ç¾ããã¨ããã¨ä»¥ä¸ã®ããã«å®ç¾©ãããã¨ã«ãªãã¾ãã
type User = { id: number | null name?: string email?: string }
ãã®å ´åããã¨ãã° ID ã null ã«ãé¢ããã name ã email ã null ã§ãªããã¨ãããããããªãç¶æ ãã表ç¾ã§ãã¦ãã¾ãã¾ãã
ãã㯠Record åã AND (ç©) ã«åºã¥ãããã¼ã¿æ§é ã®å®£è¨ã§ããã3 ã¤ã®ããããã£ããããããããã»ãªããã® 2ãã¿ã¼ã³ãåãããã®ç©ã§åè¨ 8 ãã¿ã¼ã³ã®ç¶æ ãåãã¦ãã¾ããã¨ã«èµ·å ãã¦ãã¾ãã8ãã¿ã¼ã³ã®ç¶æ ã®ä¸ã«ã¯ãå®éã«ã¯ããå¾ãªãç¶æ ãå«ã¾ãã¾ãããããã» ãªããã®åå²ã¯ ID ã«é¢ãã¦ã ãã§ããã®ã«ãã»ãã® 2 ã¤ã®ããããã£ã¾ã§ããã«å·»ãè¾¼ã¾ãã¦ãã¾ã£ãçµæã§ãã
Union å㯠OR (å) ã«åºã¥ãåä½µãªã®ã§ãIDãååãã¡ã¼ã«ã¢ãã¬ã¹ãããã Member ã«ããããããã£ããªãã Guest ã®ç¶æ ãã足ãã¦ãããã ããç¶æ ã®ç©ã¯åãã¾ããããã£ã¦åä½µãã¦ãç¶æ ãå¿ è¦ä»¥ä¸ã«å¢ãã¾ããã
Making illegal states unrepresentable (ããããªãç¶æ ã表ç¾ããªã) ã¨ããã®ã¯ãããããã¨ã§ãã
å®éã®ã¦ã¼ã¹ã±ã¼ã¹ ··· çµµæåã¢ã¤ã³ã³ãããªãã®è¡¨ç¾
ããã²ã¨ã¤ãæã ã®å®éã®ã¢ããªã±ã¼ã·ã§ã³ã§ã®å®ä¾ã®ä¸ãããç°¡åãªãã®ãç´¹ä»ãã¾ãã
æã ã®ä½ã£ã¦ã飲é£åºåãäºç´å°å¸³ã·ã¹ãã ã«ã¯é¡§å®¢ç®¡çã®æ©è½ãããã¾ããã顧客ã«ã¿ã°ä»ããã¦åé¡ãããã¨ãã§ãã¾ããã¿ã°ã¯è¦èªæ§åä¸ã®ããçµµæåãè¨å®ã§ããããã«ãªã£ã¦ãã¾ãã
ã¿ã°ãæ°ããä½ãã¨ãã¯çµµæåãè¨å®ãããã¨ãã§ãã¾ããçµµæåã¯è¨å®ãã¦ããããªãã¦ã OK ã¨ããä»æ§ã«ãªã£ã¦ãã¾ãã
ãã¦ããã®ã¿ã°ç¨ã®ã¢ã¤ã³ã³ã§ãã TagIcon
ã®ãã¼ã¿ãã©ã管çããããåãèãã¾ãã
ãã¢ã¤ã³ã³ããªããã¨ããã®ã null ã§è¡¨ç¾ãããã¨ããã¡ã§ããããã¢ã¤ã³ã³ãªããã¨ããç¶æ
ã¯ããã¯ããã§åå¨ããç¶æ
ã¨èãããã¨ãã§ãã¾ããããã NoIcon
ã¨ããåã«ãã¦ã¿ã¾ããããªãããããããã¨ã¿ãªããã¨ã§ãç¶æ
ãå®ç¾©ãããã¨ãã§ãã¾ããã
çµæã以ä¸ã®ããã« Union ã§è¡¨ç¾ãããã¨ãã§ããã§ããããããã㦠null ã«æå³ãæããããã¨ãåé¿ãã¾ãã
type TagIcon = EmojiIcon | NoIcon type EmojiIcon = { kind: "Emoji" symbol: string } type NoIcon = { kind: "NoIcon" }
åã宣è¨ããããã«ã¯ããã®åã®å¤ãçæã§ããããã«ãã¾ããããã³ã³ã¹ãã©ã¯ã¿é¢æ°ãå®ç¾©ãã¾ãããã®ã¨ããååã¨é¢æ°åãåãã«ãã ã³ã³ãããªã³ãªãã¸ã§ã¯ããã¿ã¼ã³ ã使ãã¨è¯ãã§ãã
function EmojiIcon(symbol: string): EmojiIcon { return { kind: "Emoji", symbol } } function NoIcon(): NoIcon { return { kind: "NoIcon" } }
å°ã話ããè±ç·ãã¾ãããEmojiIcon ã® symbol ã®æååã確ãã«çµµæåãã©ããããã§ãã¯ãããã¨ã§ãå¤ã®å®å ¨æ§ãããå³å¯ã«ãããã¨ãã§ãã¾ãã
function EmojiIcon(symbol: string): Result<EmojiIcon, ValidationError> { return symbol.match(/\p{Emoji}/gu) ? ok({ kind: "Emoji", symbol }) : err(new ValidationError('Emoji ã§ã¯ããã¾ãã')) }
ãããã¯ãã®å®è£ ã§ã¯ãããã¦ãã¾ãããä¾å¤ãã©ãæ±ãããªã©æ¬ç¨¿ã¨ã¯é¢ä¿ã®ãªããããã¯ãåºã¦ãã¦ãã¾ãã®ã§ä»¥éçç¥ãã¾ãã
ãã¨ããããã§åãã¤ã¾ãã¯å¤ã®æ§é ã®å®ç¾©ã¨ãã®çææ¹æ³ãå®ç¾©ã§ãã¾ããããã¨ã¯å
ã«ã¿ã User ã®ä¾ã®ããã«ãã¢ã¤ã³ã³ãçµµæåãã»çµµæåãªããã§å¦çãåãåãããã¨ã㯠kind
ããããã£ã§ãã¿ã¼ã³ãããçã«å解ããã°ããã§ãã
function toHTMLIcon(icon: TagIcon): string { switch (icon.kind) { case "Emoji": return icon.symbol case "NoIcon": return "" default: assertNever(icon) } } export function assertNever(_: never): never { throw new Error("Unexpected value. Should have been never.") }
追å ã®ä»æ§ã§çµµæåã ãã§ãªãããªãªã¸ãã«ã®ã¢ãããã¼ãç»åãæ±ãããã¨ãã¾ãããããã®å ´å㯠Union ã«æ°ãã« ImageIcon
åã追å ããã°ããã§ãããã
type TagIcon = EmojiIcon | NoIcon | ImageIcon // ImageIcon ãæ°ãã«ä½µå type EmojiIcon = { kind: "Emoji" symbol: string } type NoIcon = { kind: "NoIcon" } // ããã追å type ImageIcon = { kind: "Image" url: string name: string }
ImageIcon
åã Union ã«è¿½å ããã¨ããã¿ã¼ã³ããããã¦ããåå²ã§ç¶²ç¾
æ§ãã§ãã¯ãåããæå¾
éããã³ã³ãã¤ã«ãéããªããªãã¾ããåã«å¿ããå¦çã追å ãã¾ãã
function toHTMLIcon(icon: TagIcon): string { switch (icon.kind) { case "Emoji": return icon.symbol case "NoIcon": return "" case "Image": // ããã追å ããªãã¨ã³ã³ãã¤ã«ã¨ã©ã¼ return `<img src="${icon.url}" alt="${icon.name}" />` default: assertNever(icon) } }
å®éã«ä½ã£ãåãå¤ã¨ãã¦ä½¿ãå ´åã¯ã以ä¸ã®ãããªä½¿ãæ¹ã«ãªãã¾ãã
const icon1 = EmojiIcon("ð£") const icon2 = NoIcon() const icon3 = ImageIcon("https://example.com/image.png", "Example Image") console.log(toHTMLIcon(icon1)) // ð£ console.log(toHTMLIcon(icon2)) // console.log(toHTMLIcon(icon3)) // <img src="https://example.com/image.png" alt="Example Image" />
Discriminated Union ã«ããåãæ§é åããã³ã³ãããªã³ãªãã¸ã§ã¯ããã¿ã¼ã³ã§çæãå®è£ ããswitch æã«ããããã¼ã¤ã³ã°ã§ãã¿ã¼ã³ãããçã«å解ãå®è£ ãã¾ãããnull ã使ãã NoIcon ã¨ããç¶æ ãå°å ¥ãããããã§è¦éããããéçæ¤æ»ãæåã«æ´»ç¨ããªããå®è£ ã§ãã¾ããã
ãã£ã¹ã¯ãªããã¼ã¿ã¼ã¯ããã ã®å¤å¥ç¨ã®ã·ã³ãã«?
ããã¾ã§ã§ãååãDiscriminated Union ã®æç¨æ§ã確èªã§ãã¾ãããä»çµã¿ã¨ãã¦ã¯ãªãã¸ã§ã¯ãã®ããããã£ã« kind
ãªã©é©å½ãªããããã£åã§ãã£ã¹ã¯ãªããã¼ã¿ã¼ãå¿ã°ããç¨åº¦ã«ãè¦ãã¾ãã
TypeScript ã¬ã¤ã¤ã§ã¯ããã¼ã¤ã³ã°ã«ãã£ã¦åãã§ãã¯ãå¹ããªã©ä¸æããã¨æ©è½ãã¦ãã¦åº§å¸å£ä¸æ! ã¨ããæã (?) ãããã¾ãããJavaScript ã®ã¬ã¤ã¤ã¼ã§ã¿ãã¨ãã ãªãã¸ã§ã¯ãã®ããããã£ã®æååã§åå²ãã¦ããã ãã®ããã«ãæãã¦ããããªã«æ¬è³ªçãªäºæãªã®ã? ã¨ãæãã¦ãã¾ãã¾ãã
Discriminated Union ã表ç¾ã§ãããã®ã¯ããã®ç¨åº¦ã®ãã®ã¨æã£ã¦ããã°ããã®ã§ãããã? ããããã¨ãã話ãç¶ãã¦ã¿ã¦ãããã¨æãã¾ãã
Haskell ã®ãã¼ã¿å宣è¨
代æ°çãã¼ã¿åãã模å£ã§ããã TypeScript ã§ã¯ãªãã代æ°çãã¼ã¿åãåã·ã¹ãã ã«ãã¤ãã£ãã§æè¼ãã¦ããããã°ã©ãã³ã°è¨èªããã¨ãã° Haskell ã§åãå®è£ ãã©ããªãã®ããè¦ã¦ã¿ã¾ãããã
以ä¸ã®ããã«å®è£ ã§ãã¾ãã
import Text.Printf (printf) data TagIcon = NoIcon | EmojiIcon String | ImageIcon String String toHTMLIcon :: TagIcon -> String toHTMLIcon NoIcon = "" toHTMLIcon (EmojiIcon symbol) = symbol toHTMLIcon (ImageIcon url name) = printf "<img src=\"%s\" alt=\"%s\" >" url name main :: IO () main = do let icon1 = NoIcon icon2 = EmojiIcon "ð£" icon3 = ImageIcon "https://exmaple.com/image.png" "Example Image" putStrLn $ toHTMLIcon icon1 putStrLn $ toHTMLIcon icon2 putStrLn $ toHTMLIcon icon3
TypeScript ã§ã®å®è£ ã«æ¯è¼ããã¨åéãããªãçããªã£ã¦ãã¾ããã¨ã¯è¨ããã³ã¼ããçããã©ããã¯ãã¾ãéè¦ã§ã¯ããã¾ããããã詳細ã«è¦ã¦ã¿ã¾ãããã
ã¾ããTypeScript ã®ã±ã¼ã¹ã¨ã¯ç°ãªãã³ã³ã¹ãã©ã¯ã¿ã®æ示çãªå®è£ ããªããã¨ã«æ°ãã¤ãã¾ãã
ãã㦠toHTMLIcon
é¢æ°ã®å¼æ°ã§ãã¿ã¼ã³ãããããã¦ãã¾ãããTypeScript ã®ãã£ã¹ã¯ãªããã¼ã¿ã¼ã«ç¸å½ããã®ã¯æååãªãã©ã«çãªå¤ã§ã¯ãªã NoIcon
EmojiIcon
ImageIcon
ãªã©ã®ã·ã³ãã«ã§ããHaskell ã§ã¯ãããããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ãã¨å¼ã³ã¾ãããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã«ãã TagIcon
åã®å¤ãå解ãããã¨ãã§ãã¦ãã¾ãã
TagIcon
åã®å®£è¨ã«ããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã使ããã¦ãã¾ãããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã¯ãã¼ã¿åã®å½¢ç¶ãæ§é ãå®ç¾©ãããã®ã¨ãã¦ã使ããã¾ãã
data TagIcon = NoIcon | EmojiIcon String | ImageIcon String String
ããã¦å¤ãçæããã¨ããããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã使ããã¦ãã¾ãã
let icon1 = NoIcon icon2 = EmojiIcon "ð£" icon3 = ImageIcon "https://exmaple.com/image.png" "Example Image"
ãã®ããã« Haskell ã§ã¯ãã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ããã¿ã°ä»ã Unionãã«ãããã¿ã°ç¸å½ã§ããããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã¯åã«åºã¥ããå¤ã®å解ããã¼ã¿åã®æ§ç¯ãå¤ã®çæã¨ããã¼ã¿åã«ã¾ã¤ããæä½ãæä¾ãããã®ã«ãªã£ã¦ãã¾ãã
TypeScipt 㧠Discriminated Union ã¨ã³ã³ãããªã³ãªãã¸ã§ã¯ããã¿ã¼ã³ãswitch æ ã¨è¤æ°ã®ææ³ãçµã¿åããã¦æ¨¡å£ãã¦ããæ©è½ããHaskell ã§ã¯ãã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã¨ããä»çµã¿ã«ãã£ã¦ãããå¯çµåããããçµ±ä¸çãªããã¡ã§å®ç¾ããã¦ãã¾ããããã Haskell ã«ããã代æ°çãã¼ã¿åï¼Algebraic Data Types, ADTï¼ã®ç¹å¾´ã§ãã
ãã㦠Haskell ã§ã¯æ°ããåã¨ãã¼ã¿æ§é ãå®ç¾©ããåºæ¬çãªæ¹æ³ãããã® data ãã¼ã¯ã¼ãã«ãã宣è¨ã§ãã ···ã¨ãããã¨ã¯ããã®ãã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ãä¸å¿ã¨ãã代æ°çãã¼ã¿åã®ææ³ã§ããè¤éãªãã¼ã¿æ§é ã¨ãã®åã宣è¨ãããã¨ãã§ãããã¨ãæå³ãã¾ãã
代æ°çãã¼ã¿åã§ããæ§é çãªãã¼ã¿åãæ±ã
æ°¸ç¶ãã¼ã¿ããã°ã©ãã³ã°ã¨æ°¸ç¶ãã¼ã¿æ§é - ä¸ä¼.com Developers Blog ã§ç´¹ä»ãããäºåæ¨ (ã«ããæ°¸ç¶ãã¼ã¿é å) ã®å®è£ ãè¦ã¦ã¿ã¾ããããå®è£ 詳細ã«ã¯ç«ã¡å ¥ãããé°å²æ°ã ãã¿ã¦ãããã°ããã§ãã
-- ãã¼ã¿åã®å®£è¨ data Tree a = Leaf a | Node (Tree a) (Tree a) -- æ¨ãæ ¹ããèµ°æ»ããã¿ã¼ã³ãããã¨å帰ã§è¾¿ã£ã¦ãã read :: Int -> Tree a -> a read _ (Leaf x) = x read i (Node left right) | i < size left = read i left | otherwise = read (i - size left) right write :: Int -> a -> Tree a -> Tree a write _ v (Leaf _) = Leaf v write i v (Node left right) | i < size left = Node (write i v left) right | otherwise = Node left (write (i - size left) v right) size :: Tree a -> Int size (Leaf _) = 1 size (Node left right) = size left + size right fromList :: [a] -> Tree a fromList [] = error "Cannot build tree from empty list" fromList [x] = Leaf x fromList xs = let mid = length xs `div` 2 in Node (fromList (take mid xs)) (fromList (drop mid xs)) main :: IO () main = do let arr = fromList [1 .. 8 :: Int] print $ read 3 arr -- 3 let arr' = write 3 42 arr print $ read 3 arr' -- 42 print $ read 3 arr -- 3
éè¦ãªãã¤ã³ãã¨ãã¦ã¯ãã³ã¡ã³ãã«æ¸ããã¨ãã (1) å®å ¨äºåæ¨ã®æ¨æ§é ã data ãã¼ã¯ã¼ãã®ã¿ã§å®£è¨ãã¦ãããã¨ã(2) æ¨ã®ä¸ããç®çã®ãã¼ããæ¢ãã«ããããã¿ã¼ã³ãããã§å解ããªããèµ°æ»ãã¦ãããã¨ãã® 2 ç¹ãæãããã¾ãã
ãã¼ã¿åã®å®£è¨ãæ¹ãã¦ã¿ã¦ã¿ã¾ãããã
data Tree a = Leaf a | Node (Tree a) (Tree a)
Tree åãå帰çã«å®£è¨ããã¦ããã®ããããã¾ããå帰ãã¼ã¿åã宣è¨ã§ãããããæ¨ã®ãããªãã¼ã¿æ§é ã代æ°çãã¼ã¿åã«ããæ§ç¯ãããã¨ãã§ãã¾ãã
ãã¦ããããã¦æ¨ãå®è£ ããä¾ãã¿ãã¨ä»£æ°çãã¼ã¿åã¯ãåé ã§ã¿ããããªããã ã®åãåä½µãã¦å¤å¥ããæ©è½ã¨ãããã®ã§ã¯ãªããã¾ãã«ããã¼ã¿ã®åã¨æ§é ãæ§ç¯ããããã®ãã®ãã ã¨ããã®ããããã¾ãã
åæ§ã«ãªã¹ãæ§é ã® List åãèªåã§å®è£
ãã¦ã¿ã¾ãããããªã¹ãã®èµ°æ»ã¨ãã¦å
é ã«è¦ç´ ã追å ãã cons
é¢æ°ã¨ããªã¹ãã®å¤ãããããååãã mapList
é¢æ°ãå®è£
ãã¦ã¿ã¾ãã
data List a = Empty | Cons a (List a) deriving (Show) empty :: List a empty = Empty cons :: a -> List a -> List a cons = Cons mapList :: (a -> b) -> List a -> List b mapList _ Empty = Empty mapList f (Cons x xs) = Cons (f x) (mapList f xs) -- ãã¹ãåºå main :: IO () main = do let xs = cons 1 (cons 2 (cons 3 empty)) print (mapList (* 2) xs) -- Cons 2 (Cons 4 (Cons 6 Empty))
å
ã®äºåæ¨ã«åãããdata ãã¼ã¯ã¼ãã«ããå帰ãã¼ã¿åãå®ç¾©ãã¦ãªã¹ãã®ãã¼ã¿æ§é ãæ§ç¯ãã¦ãã¾ããmapList
é¢æ°ã§ã¯ãã¿ã¼ã³ããããç¨ãã¦ãªã¹ããèµ°æ»ãããªã¹ããä¿æããå¤ã«ååé¢æ°ãé©ç¨ãã¦ãã¾ãããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ãããã¼ã¿æ§é ã®æ§ç¯ã¨ãã¿ã¼ã³ãããã«ããå解åæ¹ã«å©ç¨ããã¦ãããã¨ããããã¾ãã
ãã®ããã« Haskell ã®ãã¼ã¿åã¯ãå¤ãã©ã®ããã«æ§é åãããæå³ã¥ãããããããå®ç¾©ããæ段ã§ãããã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã¯ãã®æ段ãæä¾ããæ§ç¯ã¨å解ã¨ããåæ¹åã®æä½ãçµ±ä¸çã«æ±ããããã«ãã¾ãã
ãã®è¦³ç¹ã«ç«ã¤ã¨ããã¼ã¿åã¨ãã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã®å½¹å²ã¯æ¬¡ã®ããã«æ´çã§ãããã§ãã
- ãã¼ã¿åã¯ãããã°ã©ã å ã®ãæ¦å¿µã¢ãã«ããå®ç¾©ãã
- ãã¼ã¿ã³ã³ã¹ãã©ã¯ã¿ã¯ããã®ã¢ãã«ã®æ§ç¯ã«ã¼ã«ãæä¾ãã
- ãã¿ã¼ã³ãããã«ããå解ã¯ããã®ã¢ãã«ã解æãæä½ããæ¹æ³ãæä¾ãã
TypeScript ã«åæ§ã®ã¡ã³ã¿ã«ã¢ãã«ãæã¡è¾¼ã
Haskell ã®ãã¼ã¿åã®å®£è¨ãããã¾ã§è¦ã¦ãããæ¹ã㦠TypeScript ã«æ»ã£ã¦ãã¾ãããã代æ°çãã¼ã¿åã«å¯¾ããã¡ã³ã¿ã«ã¢ãã«ã大ããæ´æ°ããã¦ããã¯ãã§ãã
ãã®è¦ç¹ã§ãæ¹ã㦠Discriminated Union ãã代æ°çãã¼ã¿åã®æ¨¡å£ãè¦ã¦ã¿ã¾ããããã kind
ããããã£ã¯åå²ç®çã®ãã®ãã§ã¯ãªã Haskell åæ§ ããã¼ã¿åãæ§ç¯ãå解ããæ段ãã¨ãã¦æãããã¨ãã§ããã®ã§ã¯ãªãã§ãããã?
ãã¦ãTypeScript ã®åã·ã¹ãã ã Haskell åæ§ãå帰ãã¼ã¿åã¯å®£è¨ã§ãã¾ããå ã® Haskell ã§å®è£ ãããªã¹ãããTypeScript ã§ãããã¾ã§ã¿ã Discriminated Unionãã³ã³ãããªã³ãªãã¸ã§ã¯ããã¿ã¼ã³ãswitch æã«ãããã¿ã¼ã³ãããã®ã¤ãã£ãªã ã§ãå®è£ ãã¦ã¿ã¾ãã
type List<T> = Empty | Cons<T> interface Empty { kind: "Empty" } interface Cons<T> { kind: "Cons" head: T tail: List<T> } function Empty(): Empty { return { kind: "Empty" } } function Cons<T>(head: T, tail: List<T>): Cons<T> { return { kind: "Cons", head, tail } } type map = <T, U>(f: (a: T) => U, xs: List<T>) => List<U> 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() } const xs: List<number> = Cons(1, Cons(2, Cons(3, Empty()))) console.log(map(i => i * 2, xs))
以ä¸ãå®è¡çµæã§ããDiscriminated Union ã§æ§é åããããªã¹ãã¨ãåå¤ãååã«ããååãããçµæãå¾ããã¦ãã¾ãã
$ deno run -A list.ts { kind: "Cons", head: 2, tail: { kind: "Cons", head: 4, tail: { kind: "Cons", head: 6, tail: { kind: "Empty" } } } }
TypeScript ã§ãç¡çãªããå帰ãã¼ã¿æ§é ãå®è£ ã§ãã¾ããã
æ¯è¼ãã¦ã¿ã㨠TypeScript ã«ãã代æ°çãã¼ã¿åã¯æ¨¡å£ã ããã£ã¦ãHaskell ã»ã©ç°¡æ½ã«è¡¨ç¾ãããã¨ã¯ã§ãã¾ãããä¸æ¹ã§ããããã©ã®ãããªã¡ã³ã¿ã«ã¢ãã«ã§æãããã¯ãããã°ã©ãã³ã°è¨èªã®ææ³ã«ã¯å·¦å³ãããªãã§ããããããHaskell ã®ããåæ§ã«æãã¦ãããã§ããããç°¡æ½æ§ã¯åã°ãªããã®ã®ãæ©è½çã«ã¯ãã»ã©éè²ã®ãªãå®è£ ããããã¨ãã§ãã¾ããããã¡ãããããè¤éãªãã¿ã¼ã³ããããè¦ãããã®ãã©ã³ã¿ã¤ã æ§è½ã®å½±é¿ãªã©ã§ Haskell åçã¨ã¾ã§ã¯ããã¾ãããã
ç®è«è¦ã©ãããTypeScript ã® Discriminated Union ã«å¯¾ããå°è±¡ãã¢ãããã¼ããããã¨ãã§ããã§ãããã? ã§ãã¦ãããã¨ãé¡ãã¾ã ð
å®å㧠Discriminated Union ãç¨ãã¦å帰ãã¼ã¿æ§é ã宣è¨ãããã¨ããæ©ä¼ã¯ãã¾ããªãã¨ã¯æãã¾ãããããããã ã® Union ã§ä½µåãããåãå¤å¥ã§ãããã®ã¨å°ããæããã®ã§ã¯ãªããæ¬ç¨¿ã§ã¿ãéããã¼ã¿åã®æ§ç¯ã¨å解ã®è¦³ç¹ã§æãã¦ããã¨è¦ç¹ãæ¡ããã§ãããããããåºç¯å²ã«é©ç¨ãã¦ãã£ã¦ãããã®ã ã¨ãã確証ãå¾ãããã®ã§ã¯ãªããã¨æãã¾ãã
ä½è«
TypeScript 㨠Haskell ãæ¯è¼ããè¨äºããéå»ã«å¹¾ã¤ãæ¸ãã¾ããã
- TypeScriptã§ã©ãã¾ã§ãé¢æ°åããã°ã©ãã³ã°ãããã â ãæç¶ã Haskellãããèå¯ãã - ä¸ä¼.com Developers Blog
- æ°¸ç¶ãã¼ã¿ããã°ã©ãã³ã°ã¨æ°¸ç¶ãã¼ã¿æ§é - ä¸ä¼.com Developers Blog
TypeScript ã®åã·ã¹ãã 㯠JavaScript ã®ä¸ã«å¾ä»ãããããã®ã¨ãããã¨ããããé常ã«ãã©ã¯ãã£ã«ã«ã§ä¾¿å©ã§ããä¸æ¹ãå人çã«ã¯ãããæ£ããã£ã¦ãã¦ãã®å ¨ä½åãåæ©è½ã®æ¬è³ªãæ´ã¿ã«ããã¨æãã¦ãã¾ããHaskell ãªã©è¡¨ç¾ã«å¦¥åã®å°ãªãããã°ã©ãã³ã°è¨èªã¨æ¯è¼ããç¸å¯¾åãããã¨ã§ããæ·±ãç解ã«ç¹ãããã¨ã¯å¤ãã§ãã
éã«ã¾ã¨ãã㨠Haskell ããã° TypeScript æ¸ãã®ä¸éãã
— naoya (@naoya_ito) 2024å¹´11æ16æ¥
Enjoy !