Created
January 19, 2026 20:01
-
-
Save MonoidMusician/57533fc7dfd5ec7a5e90ad92b02a2c1e to your computer and use it in GitHub Desktop.
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 Parser<T> = (input: string) => [T, string] | (null | Error); | |
| function pure<O>(o: O): Parser<O> { return i => [o, i] } | |
| { | |
| // This should reconstruct a tuple type | |
| // (Is there a better way to do this? please?) | |
| // (See below, apparently there is ... kinda ...) | |
| type Reconstruct<Is extends Array<unknown>> = { | |
| [k in keyof Is & number]: Is[k] | |
| } & Array<unknown>; | |
| // This adds `Parser<_>` into each tuple field | |
| type Sequence<Is extends Array<unknown>> = { | |
| [k in keyof Is & number]: Parser<Is[k]> | |
| } & Array<Parser<unknown>>; | |
| // N-ary apply (`<*>`/`liftA2`) | |
| function apply<Is extends Array<unknown>, O>( | |
| fn: (...input: Is) => O, | |
| ...parsers: Sequence<Is> | |
| ): Parser<O> { return () => null } | |
| // Without the function it infers just fine | |
| function sequence<Is extends Array<unknown>>( | |
| ...parsers: Sequence<Is> | |
| ): Parser<Is> { return () => null } | |
| // Try to force `Is` to infer from `parsers` not `fn`... | |
| function applyR<Is extends Array<unknown>, O>( | |
| fn: (...input: Reconstruct<Is>) => O, | |
| ...parsers: Sequence<Is> | |
| ): Parser<O> { return () => null } | |
| // The simple case works: | |
| const ex0 = sequence(pure(1), pure("2")); // infers Parser<[number, string]> | |
| // But if we introduce a function, it doesn't | |
| const ex1 = apply(x => x, pure(1), pure("2")); // infers Parser<unknown> | |
| // It takes `Is` from the function ... | |
| const ex2 = apply((x: number, y: string) => x+y, pure(1), pure("2")); // infers Parser<string> | |
| // (It does check it properly) | |
| /*fails*/ const ex3 = apply((x: never, y: string) => x+y, pure(1), pure("2")); // fails, number != string | |
| // If we introduce `Reconstruct` to delay inference ... it fails to typecheck | |
| /*fails*/ const ex4 = applyR(x => x, pure(1), pure("2")); | |
| // It looks like `Reconstruct` has the wrong length-variance somehow | |
| const ex5: Reconstruct<[1, "2"]> = [1, "2"]; // works | |
| /*fails*/ const ex6: Reconstruct<[1, "2"]> = [1, "2", 3]; // fails?? | |
| const ex7: Reconstruct<[1, "2"]> = [1]; // but this works?!? | |
| const ex7_0 = ex7[0]; // 1 | "2" | |
| const ex7_1 = ex7[1]; // 1 | "2" | |
| const ex7_2 = ex7[2]; // 1 | "2" | |
| } | |
| // Okay, let's try again | |
| // https://stackoverflow.com/questions/51672504/how-to-map-a-tuple-to-another-tuple-type-in-typescript-3-0 | |
| // Use `[...unknown[]]` and preserve the `length` field | |
| { | |
| // This should reconstruct a tuple type | |
| type Reconstruct<Is extends [...unknown[]]> = { | |
| [k in keyof Is & number]: Is[k] | |
| } & [...unknown[]] & { length: Is["length"] }; | |
| // This adds `Parser<_>` into each tuple field | |
| type Sequence<Is extends [...unknown[]]> = { | |
| [k in keyof Is & number]: Parser<Is[k]> | |
| } & [...Parser<unknown>[]] & { length: Is["length"] }; | |
| // N-ary apply (`<*>`/`liftA2`) | |
| function apply<Is extends [...unknown[]], O>( | |
| fn: (...input: Is) => O, | |
| ...parsers: Sequence<Is> | |
| ): Parser<O> { return () => null } | |
| // Without the function it infers just fine | |
| function sequence<Is extends [...unknown[]]>( | |
| ...parsers: Sequence<Is> | |
| ): Parser<Is> { return () => null } | |
| // Try to force `Is` to infer from `parsers` not `fn`... | |
| function applyR<Is extends [...unknown[]], O>( | |
| fn: (...input: Reconstruct<Is>) => O, | |
| ...parsers: Sequence<Is> | |
| ): Parser<O> { return () => null } | |
| // The simple case works: | |
| const ex0 = sequence(pure(1), pure("2")); // infers Parser<[number, string]> | |
| // But if we introduce a function, it doesn't | |
| const ex1 = apply((x, y) => x, pure(1), pure("2")); // infers Parser<unknown> | |
| // It takes `Is` from the function ... | |
| const ex2 = apply((x: number, y: string) => x+y, pure(1), pure("2")); // infers Parser<string> | |
| // (It does check it properly) | |
| /*fails*/ const ex3 = apply((x: never, y: string) => x+y, pure(1), pure("2")); // fails, number != string | |
| // If we introduce `Reconstruct` to delay inference ... it fails to typecheck | |
| /*fails*/ const ex4 = applyR(x => x, pure(1), pure("2")); | |
| // This `Reconstruct` | |
| const ex5: Reconstruct<[1, "2"]> = [1, "2"]; // works | |
| /*fails*/ const ex6: Reconstruct<[1, "2"]> = [1, "2", 3]; // fails?? | |
| /*fails*/ const ex7: Reconstruct<[1, "2"]> = [1]; // okay, now this fails too | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment