ãã¡ãã¯ã¨ã ã¹ãªã¼ Advent Calendar 2024 9æ¥ç®ã®è¨äºã§ãã
ããã«ã¡ã¯ã4æã«æ°åã§ãã¸ã¹ããã¼ã ã«ã¸ã§ã¤ã³ããã½ããã¦ã§ã¢ã¨ã³ã¸ãã¢ã®æ± 奥ã§ããå æ¥äº¬é½ã§éå¬ãããTSKaigi Kansai 2024ã§ã¯ã³ã¢ã¹ã¿ããã¨ãã¦æ´»åãã¦ãã¾ããã ç§ã®æå±ãããã¸ã¹ããã¼ã ã§ã¯ããã³ãã¨ã³ãã«TypeScriptãæ¡ç¨ããã¦ãããåã®æè»æ§ãæ´»ãããå®è£ ããã³ãã³è¦ããã¾ããããã§ä»åã¯ãTypeScriptã®åã¨ãã£ã¨ä»²è¯ããªãã¹ããåã¬ãã«ã§ååæ¼ç®ã®ãã¼ãµã¼ããå®è¡å¨ã¾ã§å®è£ ãã話ããéããã¾ãã
ä»åä½æããããã°ã©ã ãå©ç¨ããã¨ã次ã®ãããªè¨ç®ãåã¬ãã«ã§è¡ããã¨ãã§ãã¾ãã
// ååæ¼ç®ï¼ã¨ããã¤ã¤å²ãç®ã¯æªå®è£ ...ï½½ï¾ï¾ï½¾ï¾ï¼ type Result1 = Eval<"12*3+5*7+(2+45)*2-4"> // ^ 161 // æååãæ±ãã¾ãããã©ãã¼ãã¦ã type Result2 = Eval<"len('@m3_engineering')"> // ^ 15 // castãlenãªã©ã®çµã¿è¾¼ã¿é¢æ°ãå¤æ°ãä½ã£ã¦ã¿ã¾ãã type Result3 = Eval<"word+'ã®æåæ°ã¯'+cast(len(word),'str')+'æåã§ã'", { word: "Alice in Wonderland" }> // ^ "Alice in Wonderlandã®æåæ°ã¯19æåã§ã" // æ¯è¼æ¼ç®ããã¼ã«å¤ããã¡ãã type Result4 = Eval<"len('@m3_engineering')<=2*2*2*2"> // ^ true
å®ã¯ååæ¼ç®ã ãã§ãªããæ¯è¼æ¼ç®åãåªå é ä½ãæå®ããæ¬å¼§ããµãã¼ããã¦ãã¾ããã»ãã«ããæååããã¼ã«å¤ãæ±ããããçµã¿è¾¼ã¿é¢æ°ï¼lenãcastï¼ããµãã¼ããã¦ããããè©ä¾¡æã«å¤æ°ã®å¤ãå¤é¨ããæå®ã§ãããããæ©è½ãåãã¦ãã¾ãããªããå²ãç®ã¯èªç¶æ°ã®ç¯å²ããå¤ãã¦ãã¾ããããµãã¼ãå¤ã¨ãã¾ããã
ã¯ããã«
ãã®è¨äºã§ã¯ã大ã¾ããªå¦çã®æµãã説æãã¦ããããã¨æãã¾ããå®éã®ã³ã¼ã㯠GitHub - yuta-ike/ts-type-level-parser ã«ç½®ãã¦ããã¾ãã
å®éã«åããã¦ã¿ããå ´åããã¬ãã¸ããªã®READMEã«TypeScript Playgroundã¸ã®ãªã³ã¯ãããã¾ãã®ã§ãå©ç¨ãã ããï¼
åæç¥è: åã¬ãã«ã®ãã¿ã¼ã³ããã
TypeScriptï¼ã®åã¬ãã«ï¼ã§ã¯æååã®ãã¿ã¼ã³ããããå®è£ ãããã¨ãã§ãã¾ãããã¨ãã°æ¬¡ã®ã³ã¼ãã§ã¯ãããæååï¼æ£ç¢ºã«ã¯æååã®ãªãã©ã«åï¼ãä¸ããããã¨ãããã®æååã@ã¾ãã¯#ããå§ã¾ãå ´åã«ããã以éã«ç¶ãæååãåãåºãåã¨ãªã£ã¦ãã¾ããTypeScriptã«è©³ãããªãã¦ããé°å²æ°ã¯æ´ãã§ããã ããã®ã§ã¯ãªãã§ããããã
type Example<T extends string> = T extends `@${infer Username}` ? Username : T extends `#${infer Hashtag}` ? Hashtag : never type Result1 = Example<"@m3_engineering"> // "m3_engineering" type Result2 = Example<"#ã¢ããã³ãã«ã¬ã³ãã¼"> // "ã¢ããã³ãã«ã¬ã³ãã¼" type Result3 = Example<"ã¨ã ã¹ãªã¼"> // never
@${infer Username}
ã¯Template Literal Typesï¼ãã³ãã¬ã¼ããªãã©ã«åï¼ã¨å¼ã°ãã¾ãããã³ãã¬ã¼ããªãã©ã«èªä½ã¯JavaScriptã«ããææ³ã§ããããã®åãã¼ã¸ã§ã³ã¨ããä½ç½®ä»ãã§ãã
A extends B ? C : D
ã¯Conditional Typesã¨å¼ã°ããåã¬ãã«ã§æ¡ä»¶åå²ãå®ç¾ã§ãã¾ããAåãBåã«ä»£å
¥å¯è½ãªå ´åã¯Cåããããã§ãªãå ´åã¯Dåãè¿ãã¾ãã
infer
㯠extendsã®å³è¾ºã®ä½ç½®ã§å©ç¨ã§ãããã¼ã¯ã¼ãã§ãããããããé¨åãæ°ããåå¤æ°ã«ãã¤ã³ãã§ãããããªæ©è½ã§ããTypeScript4.7ããã¯ãããã«extendsãã¼ã¯ã¼ããä¼´ããã¨ãã§ããããä¸æ®µç´°ãããã¿ã¼ã³ããããç°¡åã«æ¸ããããã«ãªãã¾ããã
ãããå©ç¨ããã¨ã次ã®ããã«ã1æ¡ã®æ°åã®ã¿ãåå¾ãããã¨ããå¦çãã·ã³ãã«ã«å®ç¾ã§ãã¾ãã
type DigitValue = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" type Example<T extends string> = T extends `+${infer N extends DigitValue}` ? N : never type Result1 = Example<"+1"> // "1" type Result2 = Example<"+99"> // never type Result2 = Example<"+abc"> // never
å¦çã®æµã
ä»åã®å¦çç³»ã¯ãTokenizerãParserãEvaluatorã®3ã¤ã®é¨åã«åããã¦ãã¾ãã Tokenizerã¯æååã®ããã°ã©ã ãåãåãããã¼ã¯ã³ã«å¤æãã¾ãã+ã&&ãªã©ã®è¨å·ãæååãæ°åã®ãªãã©ã«ãªã©ãæ±ããããå½¢ã«å¤æãã¾ãã
Parserã¯ãåãåã£ããã¼ã¯ã³åãæ½è±¡æ§ææ¨ï¼ASTï¼ã®ããªã¼æ§é ã«å¤æãã¾ãããã¡ãããã¿ã¼ã³ããããå¤ç¨ãã¦å®è£ ãã¾ãã
æå¾ã«Evaluatorã§ããæ½è±¡æ§ææ¨ï¼ASTï¼ãå ã«å®éã«è¨ç®ãè¡ãã¾ããè¨ç®ã®å¦çèªä½ã¯ASTãå·¡åãã¦ããã ããªã®ã§ã·ã³ãã«ã§ãããè¨ç®å¦çèªä½ãåã¬ãã«ã§è¡ããªããã°ãªããããã¡ãã®å¯¾å¿ã®æ¹ã大å¤ã§ããã
Tokenizer
ä»åã®ã³ã¼ããã¼ã¹å ¨ä½ã¯GitHubã«ã¢ãããã¦ããã®ã§ãã¡ããè¦ã¦ãã ããã以ä¸ã¯Tokenizerã®æç²ã§ããå¦çã¯ã·ã³ãã«ã§ãåé ã§èª¬æãããããªæååãã¿ã¼ã³ããããå帰çã«å¼ã³åºãå½¢ã§ããTokenizerã«éãããåã¬ãã«ã§ã®ç¹°ãè¿ãè¨ç®ã¯åºæ¬çã«å帰ã使ãã®ã§ãé ã®ä½æã«è¯ãã§ãã
type DigitValue = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" type Tokenizer<Program extends string> = Program extends "" ? [] : Program extends `${infer Num extends DigitValue}${infer Rest extends string}`? [NumLiteral<Num>, ...Tokenizer<Rest>] : Program extends `+${infer Rest extends string}` ? [Operator<Plus>, ...Tokenizer<Rest>] : Program extends `-${infer Rest extends string}` ? [Operator<Minus>, ...Tokenizer<Rest>] : Program extends `*${infer Rest extends string}` ? [Operator<Mul>, ...Tokenizer<Rest>] : [Err<never, `Unknown token: ${Program}`>]
ã¡ãªã¿ã«ãã¡ãã®å®è£ ã§ã¯ãæ°åã¯1æ¡ã®ã¿ã¨ãã¦ãã¾ããè¤æ°æ¡ãåãå ¥ããã«ã¯è¿½å ã®å¦çãå¿ è¦ã«ãªãã¾ãã®ã§ãæ°ã«ãªãæ¹ã¯GitHubããåç §ãã ããã
Tokenizerãå®è£ ããã¨ã次ã®ãããªåºåãå¾ãããããã«ãªãã¾ãããã ã®æååã®æ°å¼ããå¾ç¶ã®Parserã§æ±ãããããããªãã¼ã¯ã³åã«å¤æããã¦ãããã¨ãåããã¾ãã
type Result = Tokenizer<"3+9*7"> // Resultåã®æ¨è«çµæ [NumLiteral<"3">, Operator<typeof plus>, NumLiteral<"7">, Operator<typeof mul>, NumLiteral<"9">]
Parser
ç¶ãã¦ãã¼ãµã¼ã§ããå¦çç³»ã®èã¨ãªãé¨åã§ãããã¾ãã¯ååæ¼ç®ã®ã¿ãèãã¾ããBNFè¨æ³ã£ã½ã表ç¾ããã¨æ¬¡ã®ããã«ãªãã¾ããï¼å®éã¯ããããææ³ãæ¡å¼µããããããã£ã¨è¤éã«ãªã£ã¦ãã¾ãï¼
expr = expr "+" term | expr "-" term | term term = term "*" factor | factor factor = num | "(" expr ")" num = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
ç»å ´äººç©ã¯ exprï¼è¶³ãç®ã»å¼ãç®ï¼ãtermï¼æãç®ï¼ãfactorãnumã®4ã¤ã§ããããããã®ãã¼ããã¨ã«ãParserãæ¸ãã¦ããã¾ãã
Termã¯ä»¥ä¸ã®ãããªå½¢ã«ãªãã¾ããtermã¯ãæãç®ããããã¿ã¼ã³ã¨ãfactorããã®ã¾ã¾æã£ã¦ãããã¿ã¼ã³ã®2éãããã¾ããããã§ãã¾ãä¸ãããããã¼ã¯ã³åãFactorParserã«é£ããã¾ããããã¦ãfactorã®æ¬¡ã«ãããã¼ã¯ã³ãã¢ã¹ã¿ãªã¹ã¯ãã©ããã§ãã©ã¡ãã®ãã¿ã¼ã³ã«å½ã¦ã¯ã¾ãããå¤æã§ãã¾ããåæ§ã®å¦çãexprãfactorãnumã®åãå®è£ ãããã¨ã§Parserãå®ç¾ã§ãã¾ãã
// Term // åºæ¬ä»æ§: Token[]ãåãåãè©ä¾¡ããã[è©ä¾¡çµæã®AST(Ast), æªè©ä¾¡ã®ãã¼ã¯ã³å(Token[])]ã®ã¿ãã«ãè¿ãã type TermParser<Tokens extends Token[]> = FactorParser<Tokens> extends [infer Ast extends AstType, [infer Op extends Operator<Mul>, ...infer Rest extends Token[]]] ? ( TermParser<Rest> extends [infer RightAst, infer RightRest extends Token[]] ? [ { type: "term", left: Ast, operator: Op right: RightAst }, RightRest ]: [ { type: "term", left: Ast, operator: Op right: Err<"Error at TermParser 2", TermParser<Rest>> }, Err<"Error at TermParser 2", TermParser<Rest>> ] ): FactorParser<Tokens> extends [infer Ast, infer Rest] ? [Ast, Rest]: Err<"Error at TermParser", FactorParser<Tokens>>
ç§ã¯å¦çç³»ã«ã¯ãã¾ã詳ãããªãã®ã§ãããååæ¼ç®ã§å¤ãç¨ããããåèµ·ä¸éæ§æ解æã¨ããã¢ã«ã´ãªãºã ãå©ç¨ãã¦ãã¾ããã¤ã³ã¿ã¼ããããæ¤ç´¢ããã¨ããããå®è£
ãè¦ã¤ããã®ã§ãããå¤ãã®å®è£
ã§ã¯ãä»ä½ãã¼ã¯ã³ç®ã¾ã§å¦çãçµãã£ã¦ãããããindexãªã©ã®intåã®å¤æ°ã§ç®¡çãã¦ãã¾ããããããåã¬ãã«ã§ã®å®è£
ã§ã¯ä¸æå¤æ°ã使ãæ¹æ³ãé£ããã§ãã
ããã§ä»åã¯ã常ã«å帰é¢æ°ã[è©ä¾¡çµæã®AST(Ast), æªè©ä¾¡ã®ãã¼ã¯ã³å(Token[])]
ã¨ããã¿ãã«ãè¿ããããªå®è£
ã«ãã¦ãã¾ããæªè©ä¾¡ã®ãã¼ã¯ã³å(Token[])ãä¿æãã¦ãããã¨ã§ãindexããªãã¦ãã©ãã¾ã§å¦çãçµãã£ã¦ããããå¤æãããã¨ãã§ãã¾ãã
Parserã¾ã§å®è£ ããã¨ã次ã®ãããªåºåãå¾ããã¾ããç¡äºãæ°å¼ãæ§ææ¨ã®å½¢ã«å¤æãããã¨ãã§ãã¾ããã
type Result = Parser<Tokenizer<"3+7*9">> // Resultåã®æ¨è«çµæ { type: "expr"; left: NumLiteral<"3">; operator: Operator<typeof plus>; right: { type: "term"; left: NumLiteral<"7">; operator: Operator<typeof mul>; right: NumLiteral<"9">; }; }
Evaluator
æå¾ã«å®è¡é¨åãå®è£ ãã¦ããã¾ããé常ã®TypeScriptã®å®è£ ã§ããã°ãæ§ææ¨ãèµ°æ»ãã¦å®éã®ååæ¼ç®ãå®è¡ãã¦ããã°çµæãæ±ã¾ãã¾ããããããåæ§ã®å¦çãåã¬ãã«ã§è¡ããã¨ããã¨ã¶ã¡å½ããåé¡ããNumberåã®æ¼ç®ãã§ããªãåé¡ã§ãã
ãã¨ãã°ãåã«1+2ãè¨ç®ãããã¨ããç´ ç´ã«ã¯ã§ãã¾ããã
type One = 1 type Two = 2 type Result = One + Two // NG: åã¬ãã«ã§ã¯"+"ã¯ä½¿ããªã
ããã§ãé åã®é·ããå©ç¨ãã¦è¨ç®ãè¡ããã¨ãèãã¾ããæ°åã®1ã¯é·ã1ã®é åãæ°åã®2ã¯é·ã2ã®é åã«å¤æããã¹ãã¬ããæ¼ç®åãå©ç¨ãã¦é åããã¼ã¸ãã¾ããã¹ãã¬ããæ¼ç®åã¯JavaScriptã®æ©è½ã§ãããTypeScriptã§ãåã¬ãã«ã§åæ§ã®æä½ãå¯è½ã§ãã
type One = [true] // é·ããæ°åã表ã type Two = [true, true] type Result = [...One, ...Two] // Resultå㯠[true, true, true] ã¨æ¨è«ããã type ResultNumber = Result["length"] // Resultåã®lengthãèªã¿åºããã¨ã§ãæ°åã«å¤æã§ãã
ãã®è¾ºãã¯ãã¸ã¹ããã¼ã ã®å ç°ãããéå»ã«TechBlogãªã©ã§ç´¹ä»ãã¦ãã¾ãã®ã§ããåç §ãã ããï¼ä»åã®ç§ã®å®è£ ã¯é常ã«ãã¤ã¼ããªãã®ã§ããããã£ã¨å·¥å¤«ã®ããããããããã§ãã https://www.m3tech.blog/entry/2022/05/27/110916 https://hrtyy.dev/anything/typed-circuit/
é åã®å½¢ã«å¤æãããã¦ãã¾ãã°ã足ãç®ã¯ãã¡ãããå¼ãç®ãæãç®ãç°¡åã§ããä¾ãã°A-Bã®å¼ãç®ã¯ãAã¨Bãã1ã¥ã¤å¼ãã¦ããæä½ãç¹°ãè¿ããBã0ã«ãªãã¾ã§å帰ãã¾ãã ã¨ã¯ãããnumberåã使ããªãããã«ãèªç¶æ°ããã®æ¼ç®ãããã®åã®éã0ããå®ç¾©ãã¦ãããã¨ã«ãªããå°ã大å¤ã§ãã
declare const n:unique symbol type N = typeof n type Num = N[] type DigitToNat<Digi extends DigitValue> = Digi extends "0" ? [] : Digi extends "1" ? [N] : Digi extends "2" ? [N,N,] : Digi extends "3" ? [N,N,N,] : Digi extends "4" ? [N,N,N,N,] : Digi extends "5" ? [N,N,N,N,N,] : Digi extends "6" ? [N,N,N,N,N,N,] : Digi extends "7" ? [N,N,N,N,N,N,N,] : Digi extends "8" ? [N,N,N,N,N,N,N,N,] : Digi extends "9" ? [N,N,N,N,N,N,N,N,N,] : never type NatToNum<Nat extends N[]> = Nat["length"] type NatAdd<A extends N[], B extends N[]> = [...A, ...B] type NatSub<A extends N[], B extends N[]> = [A, B] extends [[any, ...infer RestA extends N[]], [any, ...infer RestB extends N[]]] ? NatSub<RestA, RestB>: B extends [] ? A: never type NatMul<A extends N[], B extends N[]> = B extends [any, ...infer Rest extends N[]] ? [...A, ...NatMul<A, Rest>] : []
æå¾ã«ãParserãåºåããæ½è±¡æ§ææ¨ããå®éã«èµ°æ»ããªããè¨ç®ãã¦ãããã§ã¼ãºã«ãªãã¾ãããã¡ããæ¨æ§é ãå帰çã«ãã©ããªããå°éã«è¨ç®ãã¦ããã¾ããç¾æ®µéã§ã¯ä¸»ã«æ¼ç®åãè¦ãªããå®éã®è¨ç®ãé©ç¨ãã¦ããã ãã§ãã
type EvaluatorRec<Ast extends AstType> = Ast extends {type: "num"} ? DigitToNat<Ast["value"]> : Ast extends {type: "term", operator: Operator<Mul>, left: infer Left extends AstType, right: infer Right extends AstType} ? ( [EvaluatorRec<Left>, EvaluatorRec<Right>] extends [infer L extends Num, infer R extends Num] ? NatMul<L, R> : never ): Ast extends {type: "expr", operator: Operator<Plus>, left: infer Left extends AstType, right: infer Right extends AstType} ? ( [EvaluatorRec<Left>, EvaluatorRec<Right>] extends [infer L extends Num, infer R extends Num] ? NatAdd<L, R> : never ): Ast extends {type: "expr", operator: Operator<Minus>, left: infer Left extends AstType, right: infer Right extends AstType} ? ( [EvaluatorRec<Left>, EvaluatorRec<Right>] extends [infer L extends Num, infer R extends Num] ? NatSub<L, R> : never ): never type Evaluator<Ast extends AstType> = EvaluatorRec<Ast> extends infer Result extends N[] ? NatToNum<Result> : never
å®è£ ã®æ¦è¦³ã¯ä»¥ä¸ã¨ãªãã¾ãã
æå¾ã«ãTokenizerãParserãEvaluatorãçµã¿åããã¦å®è¡ããã¨ãæ°å¼ã®çããå¾ããã¨ãã§ãã¾ããï¼
type Y = Evaluator<Parser<Tokenizer<"8-4+3">>> // ^ 1
ãã¹ã
åã¬ãã«ã§ãã£ã¦ãããã¹ãã¯æ¬ ããã¾ãããä»åã¯ãType Challengeã®æ£èª¤å¤å®ã§å©ç¨ããã¦ããExpectåãEqualåãå©ç¨ãã¾ããããã®å®è£ ã¯npmã§ãå ¬éããã¦ãããä»åã®éçºç°å¢ã§ããTypeScript Playgroundå ã§ç´æ¥importãã¦å©ç¨ãã¾ããã
import type { Equal, Expect } from '@type-challenges/utils' type cases2 = [ // è¤éãªæ¼ç®ãã§ãã Expect<Equal<Eval<"4*3+2">, 14>>, Expect<Equal<Eval<"4+3*2">, 10>>, Expect<Equal<Eval<"4*3-2">, 10>>, ]
æ¡å¼µ
主ã«ä»¥ä¸ã®æ¡å¼µãè¡ãã¾ããã
- è¤æ°æ¡ã®èªç¶æ°ãæ±ããããã«ãã
- Boolå¤ã®å¦çã追å ããæ¯è¼æ¼ç®åï¼=ã!=ã<ã>ã<=ã>=ï¼ãè«çæ¼ç®åï¼&&, ||ï¼ã追å ãã¾ãããNumberåã¨éããBoolåã¯åã¬ãã«ã§ã®è¨ç®ã容æãªã®ã§å®è£ ã¯ç°¡åã§ããã
- æååãªãã©ã«ã»æååã®æ¼ç®ï¼+ï¼ãå®è£ ã+ãèªç¶æ°ã¨æååã®ä¸¡æ¹ã§å©ç¨ãããããããªã¢ã¼ãã£ãã¯ã«ãªãã¾ãã
- é¢æ°å¼ã³åºãã®æ§æã追å ã2種é¡ã®çµã¿è¾¼ã¿é¢æ°ï¼æååã®é·ããè¿ãlenã¨ãæååã¨èªç¶æ°ã®å¤æãè¡ãcastï¼ã追å ã()ãæ°å¼ã®åªå é ä½ã®æå®ã¨é¢æ°å¼ã³åºãã®ä¸¡æ¹ã§å©ç¨ãããããããªã¢ã¼ãã©ãã¯ã«ãªãã¾ãã
- å¤æ°ã追å ãå¤é¨ããç°å¢å¤æ°ãå ¥åã§ããä»çµã¿ã追å ã{a: 3} ã®ãããªãªãã¸ã§ã¯ãåãä¸ãããã¨ã§ãå¼ä¸ã®aã¨ããå¤æ°ã«3ããã¤ã³ãããã¾ãã
ç¹ã«é¢æ°å¼ã³åºãã¯ä¸é¨å èªã¿çãªå®è£ ãå¿ è¦ã ã£ããã¨å¤§å¤ã§ãããä»ã®å®è£ ãæããã¦æ¬å½ã«é©åãªã®ãåãããªãã®ã§ãå¾æ¥ãã£ãã調ã¹ã¦ã¿ããã¨æãã¾ãã
ã¾ã¨ã
大å¦ã§ç¿ã£ãè¨èªå¦çç³»ã®ç¥èã¨TypeScriptã®ååãããã¦å帰åããã«ã«ä½¿ãè¯ãåé¡ã§ããï¼çãããå¹´æ«å¹´å§ã§ãã£ã¬ã³ã¸ãã¦ã¿ã¦ã¯ãããã§ããããï¼
We are hiring!!
ã¨ã ã¹ãªã¼ã§ã¯çµ¶è³ã¨ã³ã¸ãã¢ãåéä¸ã§ãï¼ TypeScriptã§ã¯ã¤ã¯ã¤ãããæ¹ãTypeScriptã®åã§ä¸ç·ã«éãã§ãããæ¹ã¯æ¯éãã¡ããããé¡ããã¾ãï¼
ãã¡ããæ°åã»ã¤ã³ã¿ã¼ã³ãåéä¸ã§ãï¼ï¼