String Literal TypesやTemplate Literal Typesを使うとGenericsや関数の引数の文字列から型を作れるという話を聞いたので、勉強も兼ねて足し算を定義してみました。
以下のような感じになります。 `Expr<"1 + 1">` が `"10"` というString Literal Typeになります。2進数の足し算のみに対応していて、未対応の式ではUnrecognizedExpression型になります。3項以上でも計算できます。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Register<TOTAL, CARRY> = [TOTAL, CARRY]; | |
type AddToRegister< | |
R extends Register<string, string>, | |
A extends string, | |
B extends string | |
> = A extends `1` | |
? B extends `1` | |
? R[1] extends `1` | |
? Register<`1${R[0]}`, `1`> | |
: Register<`0${R[0]}`, `1`> | |
: R[1] extends `1` | |
? Register<`0${R[0]}`, `1`> | |
: Register<`1${R[0]}`, ``> | |
: B extends `1` | |
? R[1] extends `1` | |
? Register<`0${R[0]}`, `1`> | |
: Register<`1${R[0]}`, ``> | |
: R[1] extends `1` | |
? Register<`1${R[0]}`, ``> | |
: Register<`0${R[0]}`, ``>; | |
type Adder< | |
A extends string, | |
B extends string, | |
R extends Register<string, string> | |
> = A extends `${infer LeaderA}0` | |
? B extends `${infer LeaderB}0` | |
? Adder<LeaderA, LeaderB, AddToRegister<R, ``, ``>> | |
: B extends `${infer LeaderB}1` | |
? Adder<LeaderA, LeaderB, AddToRegister<R, ``, `1`>> | |
: Adder<LeaderA, "", AddToRegister<R, ``, ``>> | |
: A extends `${infer LeaderA}1` | |
? B extends `${infer LeaderB}0` | |
? Adder<LeaderA, LeaderB, AddToRegister<R, `1`, ``>> | |
: B extends `${infer LeaderB}1` | |
? Adder<LeaderA, LeaderB, AddToRegister<R, `1`, `1`>> | |
: Adder<LeaderA, "", AddToRegister<R, `1`, ``>> | |
: B extends `${infer LeaderB}0` | |
? Adder<"", LeaderB, AddToRegister<R, ``, ``>> | |
: B extends `${infer LeaderB}1` | |
? Adder<"", LeaderB, AddToRegister<R, `1`, ``>> | |
: `${R[1]}${R[0]}`; | |
type Add<A extends string, B extends string> = A extends UnrecognizedExpression | |
? UnrecognizedExpression | |
: B extends UnrecognizedExpression | |
? UnrecognizedExpression | |
: Adder<A, B, Register<``, ``>>; | |
type UnrecognizedExpression = "UnrecognizedExpression"; | |
type BinaryDigit = "0" | "1"; | |
type isBinaryNumber<D> = D extends `${BinaryDigit}${infer Rest}` | |
? Rest extends "" | |
? true | |
: isBinaryNumber<Rest> | |
: false; | |
type Expr<E extends string> = E extends ` ${infer WithoutWhitespace}` | |
? Expr<WithoutWhitespace> | |
: E extends `${infer WithoutWhitespace} ` | |
? Expr<WithoutWhitespace> | |
: isBinaryNumber<E> extends true | |
? E | |
: E extends `${infer A}+${infer B}` | |
? Add<Expr<A>, Expr<B>> | |
: UnrecognizedExpression; | |
function equalsTo<T>(value: T) { | |
console.log(value); | |
} | |
equalsTo<Expr<"0 + 0">>("0"); | |
equalsTo<Expr<"0 + 1">>("1"); | |
equalsTo<Expr<"1 + 0">>("1"); | |
equalsTo<Expr<"1 + 1">>("10"); | |
equalsTo<Expr<"11 + 1">>("100"); | |
equalsTo<Expr<"11 + 11">>("110"); | |
equalsTo<Expr<"1100100 + 11101011">>("101001111"); | |
equalsTo<Expr<"1 + 1 + 1">>("11"); | |
equalsTo<Expr<"11 + 11 + 11">>("1001"); | |
equalsTo<Expr<"11 + 11 + 11 + 11">>("1100"); | |
equalsTo<Expr<"11 + 11 + 11 + 11 + 11">>("1111"); | |
equalsTo<Expr<"1 + 2">>("UnrecognizedExpression"); | |
equalsTo<Expr<"1 * 1">>("UnrecognizedExpression"); | |
export {} |
型が補完されるエディタを使っていると、答を自分で打たなくても補完されたりして楽しいです。
これをtscで変換すると以下のようになるのですが、これを見て、TypeScriptの型はほんとに実行時には影響のないものなのだなと実感することもできます。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
exports.__esModule = true; | |
function equalsTo(value) { | |
console.log(value); | |
} | |
equalsTo("0"); | |
equalsTo("1"); | |
equalsTo("1"); | |
equalsTo("10"); | |
equalsTo("100"); | |
equalsTo("110"); | |
equalsTo("101001111"); | |
equalsTo("11"); | |
equalsTo("1001"); | |
equalsTo("1100"); | |
equalsTo("1111"); | |
equalsTo("UnrecognizedExpression"); | |
equalsTo("UnrecognizedExpression"); |
足し算の定義は業務の役には立たないものの、このあたりを勉強しつつTypeScriptを使っているウェブのフレームワーク(🔥)にAdded type to c.req.param key.というPRを作ってみて、引数から型を生成するようなパターンには可能性があるなと感じたりもしています。
ちなみにTypeScriptではdocument.querySelector(`a`)と指定したときに引数からHTMLAnchorElementが推論されたりして面白いのですが、`div a`や`a[href^="#"]`だとHTMLElementになってしまうというのがあるのですが、以下のように指定すると要素名っぽいものから推論されるようにできます。(面白いけど、やっぱりこれも使う機会はないとは思いますが。)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type MyElementTagNameLookup<T extends string> = | |
T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : | |
T extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[T] : | |
Element; | |
type MyElementTagNameMapWithSuffix<T extends string> = | |
T extends `${infer TagName}[${infer _Attr}` | |
? MyElementTagNameLookup<TagName> | |
: T extends `${infer TagName}.${infer _ClassName}` | |
? MyElementTagNameLookup<TagName> | |
: MyElementTagNameLookup<T>; | |
type MyElementTagNameMap<T extends string> = | |
T extends `${infer _Head} ${infer Tail}` | |
? MyElementTagNameMap<Tail> | |
: MyElementTagNameMapWithSuffix<T>; | |
declare global { | |
interface ParentNode { | |
querySelectorAll<T extends string>( | |
selectors: T | |
): NodeListOf<MyElementTagNameMap<T>>; | |
querySelector<T extends string>( | |
selectors: T | |
): MyElementTagNameMap<T> | null | |
} | |
} | |
document.querySelectorAll(`a[href^="#"]`).forEach((e) => { alert(e.href) }); | |
document.querySelector(`input[name="nickname"]`)?.value; | |
export {}; |