Last active
July 9, 2025 19:48
-
-
Save raganwald/24d3334d6749987557ca4190023466b9 to your computer and use it in GitHub Desktop.
Proof-of-concept of working around type-level TypeScript's 1,000 invocation limit on tail-recursive types
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
// --------- Utilities --------- | |
type Expect<T extends true> = T; | |
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 | |
? true | |
: false; | |
type Extends<A, B> = A extends B ? true : false; | |
type IsNever<T> = Extends<[T], [never]>; | |
type IsFalse<T> = T extends false ? true : false; | |
type FAIL = { type: "FAIL" }; | |
type Fail<M extends string> = FAIL & { message: M }; | |
type IsFail<T> = [T] extends [FAIL] ? true : false; | |
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`; | |
// ---------- Foundational Types ---------- | |
type BlankOr<T> = "" | T; | |
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; | |
type StartsWithDigit = `${Digit}${string}`; // can start with a zero | |
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers | |
namespace TestWholeNumber { | |
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>; | |
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>; | |
type _6 = Expect<Extends<"6", WholeNumber>>; | |
type _42 = Expect<Extends<"42", WholeNumber>>; | |
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>; | |
} | |
type AsWholeNumber<T extends string> = | |
`${T}` extends infer WholeNumberT extends WholeNumber | |
? WholeNumberT | |
: never; | |
// ---------- Jackson's Widowbird ---------- | |
type DepthLimit = 100; | |
type DepthZero = []; | |
type Depth = unknown[]; | |
type Deeper<T extends Depth> = [...T, unknown]; | |
type IsDepthAtLimit<T extends Depth> = T["length"] extends DepthLimit ? true : false; | |
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] }; | |
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params }; | |
// ---------- Operations on Foundational Types ---------- | |
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">; | |
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> = | |
SplitLastDigitImpl<N, ButLast, DepthZero> extends infer ReturnValue | |
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] } | |
? SplitLastDigitTop<N2, ButLast2> | |
: ReturnValue | |
: never; | |
type SplitLastDigitImpl<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>, D extends unknown[]> = | |
N extends Digit | |
? [ButLast, N] | |
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}` | |
? IsDepthAtLimit<D> extends true | |
? ReturnToTopWith<[N, ButLast]> | |
: SplitLastDigitImpl<ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>> | |
: never; | |
namespace TestSplitLastDigit { | |
type TenThousandCharNumber = RepeatTenTimes<RepeatTenTimes<RepeatTenTimes<"1234567890">>>; | |
type TenThousandAndOneCharNumber = `${TenThousandCharNumber}6`; | |
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<TenThousandAndOneCharNumber>>>; | |
} |
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
// --------- Utilities --------- | |
type Expect<T extends true> = T; | |
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 | |
? true | |
: false; | |
type Extends<A, B> = A extends B ? true : false; | |
type IsNever<T> = Extends<[T], [never]>; | |
type IsFalse<T> = T extends false ? true : false; | |
type Assume<T, U> = T extends U ? T : U; | |
type FAIL = { type: "FAIL" }; | |
type Fail<M extends string> = FAIL & { message: M }; | |
type IsFail<T> = [T] extends [FAIL] ? true : false; | |
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`; | |
// ---------- Foundational Types ---------- | |
type BlankOr<T> = "" | T; | |
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; | |
type StartsWithDigit = `${Digit}${string}`; // can start with a zero | |
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers | |
namespace TestWholeNumber { | |
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>; | |
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>; | |
type _6 = Expect<Extends<"6", WholeNumber>>; | |
type _42 = Expect<Extends<"42", WholeNumber>>; | |
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>; | |
} | |
type AsWholeNumber<T extends string> = | |
`${T}` extends infer WholeNumberT extends WholeNumber | |
? WholeNumberT | |
: never; | |
// ---------- Higher Kinded Types ---------- | |
// | |
// See: https://code.lol/post/programming/higher-kinded-types/$0 | |
// | |
type GenericFunction = (..._: never[]) => unknown; | |
abstract class HKT { | |
readonly _1?: unknown; | |
readonly _2?: unknown; | |
readonly _3?: unknown; | |
readonly _4?: unknown; | |
new?: GenericFunction; | |
} | |
// quite the hack | |
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType< | |
(F & | |
{ | |
readonly _1: _1; | |
readonly _2: _2; | |
readonly _3: _3; | |
readonly _4: _4; | |
}) extends infer G extends { new: GenericFunction } ? G["new"] : never | |
>; | |
// ---------- Jackson's Widowbird ---------- | |
// | |
// This preliminary version relies on the recursive type incorporating the | |
// "widowbird" behaviour of jumping back up to the top inline with the | |
// "business logic" of the recursive type itself. | |
// | |
// Non-trivial to-do: | |
// | |
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding | |
// a self-referential type, and; | |
// 2. Writing "top" such that it can encode all this logic into a decorated | |
// "myself" that manages depth and knows when to go ack to the top. | |
type DepthLimit = RepeatTenTimes<RepeatTenTimes<".">>; | |
type DepthZero = ""; | |
type Depth = string; | |
type Deeper<T extends Depth> = `${T}.`; | |
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>; | |
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] }; | |
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params }; | |
// ---------- Operations on Foundational Types ---------- | |
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">; | |
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> = | |
Apply<SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue | |
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] } | |
? SplitLastDigitTop<N2, ButLast2> | |
: ReturnValue | |
: never; | |
// ---------- Wrapping SplitLastDigitImpl into an HKT ---------- | |
interface SplitLastDigitHKT extends HKT { | |
new: (_N: Assume<this["_1"], StartsWithDigit>, _ButLast: Assume<this["_2"], BlankOr<WholeNumber>>, _D: Assume<this["_3"], Depth>) => | |
[typeof _N, typeof _ButLast, typeof _D] extends [infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth] | |
? IsDepthAtLimit<D> extends true | |
? ReturnToTopWith<[N, ButLast]> | |
: N extends Digit | |
? [ButLast, N] | |
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}` | |
? Apply<SplitLastDigitHKT, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>> | |
: never | |
: never; | |
} | |
namespace TestSplitLastDigit { | |
// @ts-expect-error does not start with a digit | |
type _0 = SplitLastDigit<"">; | |
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>; | |
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>; | |
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>; | |
type TenThousandCharNumber = RepeatTenTimes<RepeatTenTimes<RepeatTenTimes<"1234567890">>>; | |
type TenThousandAndOneCharNumber = `${TenThousandCharNumber}6`; | |
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<TenThousandAndOneCharNumber>>>; | |
} |
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
// --------- Utilities --------- | |
type Expect<T extends true> = T; | |
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 | |
? true | |
: false; | |
type Extends<A, B> = A extends B ? true : false; | |
type IsNever<T> = Extends<[T], [never]>; | |
type IsFalse<T> = T extends false ? true : false; | |
type Assume<T, U> = T extends U ? T : U; | |
type FAIL = { type: "FAIL" }; | |
type Fail<M extends string> = FAIL & { message: M }; | |
type IsFail<T> = [T] extends [FAIL] ? true : false; | |
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`; | |
// ---------- Foundational Types ---------- | |
type BlankOr<T> = "" | T; | |
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; | |
type StartsWithDigit = `${Digit}${string}`; // can start with a zero | |
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers | |
namespace TestWholeNumber { | |
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>; | |
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>; | |
type _6 = Expect<Extends<"6", WholeNumber>>; | |
type _42 = Expect<Extends<"42", WholeNumber>>; | |
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>; | |
} | |
type AsWholeNumber<T extends string> = | |
`${T}` extends infer WholeNumberT extends WholeNumber | |
? WholeNumberT | |
: never; | |
// ---------- Higher Kinded Types ---------- | |
// | |
// See: https://code.lol/post/programming/higher-kinded-types/$0 | |
// | |
type GenericFunction = (..._: never[]) => unknown; | |
abstract class HKT { | |
readonly _1?: unknown; | |
readonly _2?: unknown; | |
readonly _3?: unknown; | |
readonly _4?: unknown; | |
new?: GenericFunction; | |
} | |
// quite the hack | |
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType< | |
(F & | |
{ | |
readonly _1: _1; | |
readonly _2: _2; | |
readonly _3: _3; | |
readonly _4: _4; | |
}) extends infer G extends { new: GenericFunction } ? G["new"] : never | |
>; | |
// ---------- Jackson's Widowbird ---------- | |
// | |
// This preliminary version relies on the recursive type incorporating the | |
// "widowbird" behaviour of jumping back up to the top inline with the | |
// "business logic" of the recursive type itself. | |
// | |
// Non-trivial to-do: | |
// | |
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding | |
// a self-referential type, and; | |
// 2. Writing "top" such that it can encode all this logic into a decorated | |
// "myself" that manages depth and knows when to go ack to the top. | |
type DepthLimit = RepeatTenTimes<RepeatTenTimes<".">>; | |
type DepthZero = ""; | |
type Depth = string; | |
type Deeper<T extends Depth> = `${T}.`; | |
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>; | |
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] }; | |
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params }; | |
// ---------- Operations on Foundational Types ---------- | |
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">; | |
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> = | |
Apply<SplitLastDigitHKT, SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue | |
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] } | |
? SplitLastDigitTop<N2, ButLast2> | |
: ReturnValue | |
: never; | |
interface SplitLastDigitHKT extends HKT { | |
new: ( | |
_Myself: Assume<this["_1"], HKT>, _N: Assume<this["_2"], StartsWithDigit>, _ButLast: Assume<this["_3"], BlankOr<WholeNumber>>, _D: Assume<this["_4"], Depth> | |
) => | |
[ | |
typeof _Myself, typeof _N, typeof _ButLast, typeof _D | |
] extends [ | |
infer Myself extends HKT, infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth | |
] | |
? N extends Digit | |
? [ButLast, N] | |
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}` | |
? IsDepthAtLimit<D> extends true | |
? ReturnToTopWith<[N, ButLast]> | |
: Apply<SplitLastDigitHKT, SplitLastDigitHKT, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>> | |
: never | |
: never; | |
} | |
namespace TestSplitLastDigit { | |
// @ts-expect-error does not start with a digit | |
type _0 = SplitLastDigit<"">; | |
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>; | |
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>; | |
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>; | |
type TenThousandCharNumber = RepeatTenTimes<RepeatTenTimes<RepeatTenTimes<"1234567890">>>; | |
type TenThousandAndOneCharNumber = `${TenThousandCharNumber}6`; | |
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<TenThousandAndOneCharNumber>>>; | |
} |
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
// --------- Utilities --------- | |
type Expect<T extends true> = T; | |
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 | |
? true | |
: false; | |
type Extends<A, B> = A extends B ? true : false; | |
type IsNever<T> = Extends<[T], [never]>; | |
type IsFalse<T> = T extends false ? true : false; | |
type Assume<T, U> = T extends U ? T : U; | |
type FAIL = { type: "FAIL" }; | |
type Fail<M extends string> = FAIL & { message: M }; | |
type IsFail<T> = [T] extends [FAIL] ? true : false; | |
// ---------- Counting small(!) numbers with strings | |
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`; | |
// ---------- Foundational Types ---------- | |
type BlankOr<T> = "" | T; | |
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; | |
type StartsWithDigit = `${Digit}${string}`; // can start with a zero | |
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers | |
namespace TestWholeNumber { | |
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>; | |
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>; | |
type _6 = Expect<Extends<"6", WholeNumber>>; | |
type _42 = Expect<Extends<"42", WholeNumber>>; | |
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>; | |
} | |
type AsWholeNumber<T extends string> = | |
`${T}` extends infer WholeNumberT extends WholeNumber | |
? WholeNumberT | |
: never; | |
// ---------- Higher Kinded Types ---------- | |
// | |
// See: https://code.lol/post/programming/higher-kinded-types/$0 | |
// | |
type GenericFunction = (..._: never[]) => unknown; | |
abstract class HKT { | |
readonly _1?: unknown; | |
readonly _2?: unknown; | |
readonly _3?: unknown; | |
readonly _4?: unknown; | |
new?: GenericFunction; | |
} | |
// quite the hack | |
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType< | |
(F & | |
{ | |
readonly _1: _1; | |
readonly _2: _2; | |
readonly _3: _3; | |
readonly _4: _4; | |
}) extends infer G extends { new: GenericFunction } ? G["new"] : never | |
>; | |
// ---------- Jackson's Widowbird ---------- | |
// | |
// This preliminary version relies on the recursive type incorporating the | |
// "widowbird" behaviour of jumping back up to the top inline with the | |
// "business logic" of the recursive type itself. | |
// | |
// Non-trivial to-do: | |
// | |
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding | |
// a self-referential type, and; | |
// 2. Writing "top" such that it can encode all this logic into a decorated | |
// "myself" that manages depth and knows when to go ack to the top. | |
type DepthLimit = RepeatTenTimes<".........">; | |
type DepthZero = ""; | |
type Depth = string; | |
type Deeper<T extends Depth> = `${T}.`; | |
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>; | |
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] }; | |
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params }; | |
// ---------- Operations on Foundational Types ---------- | |
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">; | |
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> = | |
Apply<SplitLastDigitHKT, SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue | |
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] } | |
? SplitLastDigitTop<N2, ButLast2> | |
: ReturnValue | |
: never; | |
interface SplitLastDigitHKT extends HKT { | |
new: ( | |
_Myself: Assume<this["_1"], HKT>, _N: Assume<this["_2"], StartsWithDigit>, _ButLast: Assume<this["_3"], BlankOr<WholeNumber>>, _D: Assume<this["_4"], Depth> | |
) => | |
[ | |
typeof _Myself, typeof _N, typeof _ButLast, typeof _D | |
] extends [ | |
infer Myself extends HKT, infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth | |
] | |
? N extends Digit | |
? [ButLast, N] | |
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}` | |
? IsDepthAtLimit<D> extends true | |
? ReturnToTopWith<[N, ButLast]> | |
: Apply<Myself, Myself, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>> | |
: never | |
: never; | |
} | |
// appears to be broken when the depth limit is 100, but works for 90! | |
namespace TestSplitLastDigit { | |
// @ts-expect-error does not start with a digit | |
type _0 = SplitLastDigit<"">; | |
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>; | |
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>; | |
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>; | |
type _4 = Expect<Equal<["123", "4"], SplitLastDigit<"1234">>>; | |
type TenCharNumber = "1234567890"; | |
type _TestTenAndOne = Expect<Equal<[TenCharNumber, "6"], SplitLastDigit<`${TenCharNumber}6`>>>; | |
type OneHundredCharNumber = RepeatTenTimes<TenCharNumber>; | |
type _TestFifty = Expect<Equal< | |
["1234567890123456789012345678901234567890123456789", "0"], | |
SplitLastDigit<"12345678901234567890123456789012345678901234567890"> | |
>>; | |
type _TestOneHundred = Expect<Equal< | |
["123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "0"], | |
SplitLastDigit<OneHundredCharNumber> | |
>>; | |
type _TestOneHundredAndOne = Expect<Equal<[OneHundredCharNumber, "6"], SplitLastDigit<`${OneHundredCharNumber}6`>>>; | |
type OneThousandCharNumber = RepeatTenTimes<OneHundredCharNumber>; | |
type _TestOneThousandAndOne = Expect<Equal<[OneThousandCharNumber, "6"], SplitLastDigit<`${OneThousandCharNumber}6`>>>; | |
type TenThousandCharNumber = RepeatTenTimes<OneThousandCharNumber>; | |
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<`${TenThousandCharNumber}6`>>>; | |
} |
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
// --------- Utilities --------- | |
type Expect<T extends true> = T; | |
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 | |
? true | |
: false; | |
type Extends<A, B> = A extends B ? true : false; | |
type IsNever<T> = Extends<[T], [never]>; | |
type IsFalse<T> = T extends false ? true : false; | |
type Assume<T, U> = T extends U ? T : U; | |
type FAIL = { type: "FAIL" }; | |
type Fail<M extends string> = FAIL & { message: M }; | |
type IsFail<T> = [T] extends [FAIL] ? true : false; | |
// ---------- Counting small(!) numbers with strings | |
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`; | |
// ---------- Foundational Types ---------- | |
type BlankOr<T> = "" | T; | |
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; | |
type StartsWithDigit = `${Digit}${string}`; // can start with a zero | |
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers | |
namespace TestWholeNumber { | |
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>; | |
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>; | |
type _6 = Expect<Extends<"6", WholeNumber>>; | |
type _42 = Expect<Extends<"42", WholeNumber>>; | |
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>; | |
} | |
type AsWholeNumber<T extends string> = | |
`${T}` extends infer WholeNumberT extends WholeNumber | |
? WholeNumberT | |
: never; | |
// ---------- Higher Kinded Types ---------- | |
// | |
// See: https://code.lol/post/programming/higher-kinded-types/$0 | |
// | |
type GenericFunction = (..._: never[]) => unknown; | |
abstract class HKT { | |
readonly _1?: unknown; | |
readonly _2?: unknown; | |
readonly _3?: unknown; | |
readonly _4?: unknown; | |
new?: GenericFunction; | |
} | |
// quite the hack | |
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType< | |
(F & | |
{ | |
readonly _1: _1; | |
readonly _2: _2; | |
readonly _3: _3; | |
readonly _4: _4; | |
}) extends infer G extends { new: GenericFunction } ? G["new"] : never | |
>; | |
// ---------- Jackson's Widowbird ---------- | |
// | |
// This preliminary version relies on the recursive type incorporating the | |
// "widowbird" behaviour of jumping back up to the top inline with the | |
// "business logic" of the recursive type itself. | |
// | |
// Non-trivial to-do: | |
// | |
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding | |
// a self-referential type, and; | |
// 2. Writing "top" such that it can encode all this logic into a decorated | |
// "myself" that manages depth and knows when to go ack to the top. | |
type DepthLimit = RepeatTenTimes<".........">; | |
type DepthZero = ""; | |
type Depth = string; | |
type Deeper<T extends Depth> = `${T}.`; | |
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>; | |
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] }; | |
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params }; | |
// ---------- Operations on Foundational Types ---------- | |
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">; | |
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> = | |
Apply<SplitLastDigitHKT, SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue | |
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] } | |
? SplitLastDigitTop<N2, ButLast2> | |
: ReturnValue | |
: never; | |
// TODO: Extract the limit check | |
// Refactor logic order | |
interface SplitLastDigitHKT extends HKT { | |
new: ( | |
_Myself: Assume<this["_1"], HKT>, _N: Assume<this["_2"], StartsWithDigit>, _ButLast: Assume<this["_3"], BlankOr<WholeNumber>>, _D: Assume<this["_4"], Depth> | |
) => | |
[ | |
typeof _Myself, typeof _N, typeof _ButLast, typeof _D | |
] extends [ | |
infer Myself extends HKT, infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth | |
] | |
? IsDepthAtLimit<D> extends true | |
? ReturnToTopWith<[N, ButLast]> | |
: N extends Digit | |
? [ButLast, N] // generate case | |
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}` | |
? Apply<Myself, Myself, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>> | |
: never | |
: never; | |
} | |
// appears to be broken when the depth limit is 100, but works for 90! | |
namespace TestSplitLastDigit { | |
// @ts-expect-error does not start with a digit | |
type _0 = SplitLastDigit<"">; | |
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>; | |
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>; | |
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>; | |
type _4 = Expect<Equal<["123", "4"], SplitLastDigit<"1234">>>; | |
type TenCharNumber = "1234567890"; | |
type _TestTenAndOne = Expect<Equal<[TenCharNumber, "6"], SplitLastDigit<`${TenCharNumber}6`>>>; | |
type OneHundredCharNumber = RepeatTenTimes<TenCharNumber>; | |
type _TestFifty = Expect<Equal< | |
["1234567890123456789012345678901234567890123456789", "0"], | |
SplitLastDigit<"12345678901234567890123456789012345678901234567890"> | |
>>; | |
type _TestOneHundred = Expect<Equal< | |
["123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "0"], | |
SplitLastDigit<OneHundredCharNumber> | |
>>; | |
type _TestOneHundredAndOne = Expect<Equal<[OneHundredCharNumber, "6"], SplitLastDigit<`${OneHundredCharNumber}6`>>>; | |
type OneThousandCharNumber = RepeatTenTimes<OneHundredCharNumber>; | |
type _TestOneThousandAndOne = Expect<Equal<[OneThousandCharNumber, "6"], SplitLastDigit<`${OneThousandCharNumber}6`>>>; | |
type TenThousandCharNumber = RepeatTenTimes<OneThousandCharNumber>; | |
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<`${TenThousandCharNumber}6`>>>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment