diff --git a/src/internals/core/Core.ts b/src/internals/core/Core.ts index 4f9f630..0f8095a 100644 --- a/src/internals/core/Core.ts +++ b/src/internals/core/Core.ts @@ -1,8 +1,8 @@ import { ExcludePlaceholders, MergeArgs } from "./impl/MergeArgs"; import { Head } from "../helpers"; -declare const rawArgs: unique symbol; -type rawArgs = typeof rawArgs; +export declare const rawArgs: unique symbol; +export type rawArgs = typeof rawArgs; /** * Base interface for all functions. @@ -36,8 +36,8 @@ export interface Fn { return: unknown; } -declare const unset: unique symbol; -declare const _: unique symbol; +export declare const unset: unique symbol; +export declare const _: unique symbol; /** * A placeholder type that can be used to indicate that a parameter is not set. diff --git a/src/internals/core/Core2.ts b/src/internals/core/Core2.ts new file mode 100644 index 0000000..b5b6409 --- /dev/null +++ b/src/internals/core/Core2.ts @@ -0,0 +1,352 @@ +import { _, unset } from "./Core"; +import { ExcludePlaceholders, ExcludeUnset, MergeArgs } from "./impl/MergeArgs"; +import * as NumberImpl from "../numbers/impl/numbers"; +import * as StringImpl from "../strings/impl/strings"; +import { Equal, Expect } from "../helpers"; + +/** + * Core + */ + +export interface Fn { + inputTypes: input; + outputType: output; + args: unknown; + return: unknown; +} + +type ExcludePlaceholdersFromInputTypes< + inputTypes extends unknown[], + partialArgs extends unknown[], + result extends unknown[] = [] +> = [inputTypes, partialArgs] extends [ + [infer fInput, ...infer rInput], + [infer fPartial, ...infer rPartial] +] + ? ExcludePlaceholdersFromInputTypes< + rInput, + rPartial, + fPartial extends _ ? [...result, fInput] : result + > + : [...result, ...inputTypes]; + +interface Ap extends Fn { + name: "Ap"; + + argsArray: Extract; + allArgs: [...partialArgs, ...this["argsArray"]]; + + expectedArgsCount: fn["inputTypes"]["length"]; + providedArgsCount: ExcludePlaceholders["length"]; + + inputTypes: ExcludePlaceholdersFromInputTypes; + + outputType: fn["outputType"]; + + isFullyApplied: NumberImpl.Compare< + this["providedArgsCount"], + this["expectedArgsCount"] + > extends 1 | 0 + ? true + : false; + + return: this["isFullyApplied"] extends true + ? Apply> + : Ap; +} + +namespace Tuple { + export type Last = xs extends [...any, infer last] ? last : never; +} + +type ApplyLeftToRight = fns extends [ + infer fn extends Fn, + ...infer restFns +] + ? ApplyLeftToRight<[Apply], restFns> + : x[0]; + +interface Piped extends Fn { + name: "Pipe"; + + inputTypes: fns[0]["inputTypes"]; + outputType: Extract, Fn>["outputType"]; + + return: ApplyLeftToRight, fns>; +} + +type GetOutputType = x extends { outputType: infer O } ? O : never; + +export type Pipe< + fn0 extends Fn, + fn1 extends Fn<[GetOutputType]> | unset = unset, + fn2 extends Fn<[GetOutputType]> | unset = unset, + fn3 extends Fn<[GetOutputType]> | unset = unset, + fn4 extends Fn<[GetOutputType]> | unset = unset, + fn5 extends Fn<[GetOutputType]> | unset = unset, + fn6 extends Fn<[GetOutputType]> | unset = unset, + fn7 extends Fn<[GetOutputType]> | unset = unset, + fn8 extends Fn<[GetOutputType]> | unset = unset, + fn9 extends Fn<[GetOutputType]> | unset = unset, + fn10 extends Fn<[GetOutputType]> | unset = unset, + fn11 extends Fn<[GetOutputType]> | unset = unset, + fn12 extends Fn<[GetOutputType]> | unset = unset, + fn13 extends Fn<[GetOutputType]> | unset = unset +> = Piped< + Extract< + ExcludeUnset< + [fn0, fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9, fn10, fn11, fn12, fn13] + >, + Fn[] + > +>; + +export type Apply = (fn & { + args: args; +})["return"]; + +type AnyAp = Ap; + +export type $< + fn extends Fn, + arg0 extends fn["inputTypes"][0] | AnyAp | _ = unset, + arg1 extends fn["inputTypes"][1] | AnyAp | _ = unset, + arg2 extends fn["inputTypes"][2] | AnyAp | _ = unset, + arg3 extends fn["inputTypes"][3] | AnyAp | _ = unset, + ap extends AnyAp = fn extends { name: "Ap" } ? fn : Ap +> = (ap & { + args: ExcludeUnset<[arg0, arg1, arg2, arg3]>; +})["return"]; + +type Args = fn["args"]; + +type Arg0 = Extract< + Extract[0], + fn["inputTypes"][0] +>; + +type Arg1 = Extract< + Extract[1], + fn["inputTypes"][1] +>; + +type Arg2 = Extract< + Extract[2], + fn["inputTypes"][2] +>; + +type Arg3 = Extract< + Extract[3], + fn["inputTypes"][3] +>; + +/** + * Playground 👇 + */ + +type ExpectNumber = [a]; +// arguments are typed internally: +interface TakeNumAndStr extends Fn<[number, string], boolean> { + works: ExpectNumber>; // ✅ + // @ts-expect-error + fails: ExpectNumber>; + // ~~~~~~~~~~ ❌ + return: true; +} + +interface Div extends Fn<[number, number], number> { + return: NumberImpl.Div, Arg1>; +} + +/** + * Full application + */ + +type t1 = $; // 5 +// ^? +type test1 = Expect>; + +// @ts-expect-error +type err1 = $; +// ~~~ ❌ + +// @ts-expect-error +type err2 = $; +// ~~~ ❌ + +/** + * Partial application in order + */ + +type Div1 = $; +type t2 = $; +// ^? +type test2 = Expect>; +// ^? + +// @ts-expect-error +type t3 = $<$, "2">; +// ~~~ ❌ + +/** + * Partial application different order + */ +type DivBy2 = $; +// ^? +type t4 = $; // 5 ✅ +// ^? +type test4 = Expect>; + +type t5 = $<$, 10, 5>; // ✅ +// ^? +type test5 = Expect>; + +type TakeStr = $; +// ^? Ap + +// @ts-expect-error +type t8 = $; +// ~~ ❌ + +type TakeNum = $; +// ^?Ap + +type t7 = $; // ✅ +type test7 = Expect>; + +// @ts-expect-error +type t8 = $; +// ~~~~ ❌ + +/** + * Higher order + */ + +interface Map extends Fn<[Fn<[A], B>, A[]], B[]> { + return: Args extends [infer fn extends Fn, infer tuple] + ? { [key in keyof tuple]: $ } + : never; +} + +type t9 = $, $, [2, 4, 6, 8, 10]>; +// ^? [1, 2, 3, 4, 5] +type test9 = Expect>; + +interface Add extends Fn<[number, number], number> { + return: NumberImpl.Add, Arg1>; +} + +interface Mul extends Fn<[number, number], number> { + return: NumberImpl.Mul, Arg1>; +} + +type ReduceImpl = xs extends [ + infer first, + ...infer rest +] + ? ReduceImpl, rest> + : acc; + +interface Reduce + extends Fn<[Fn<[B, A], B>, B, A[]], B> { + return: Args extends [infer fn extends Fn, infer acc, infer tuple] + ? ReduceImpl + : never; +} + +type t11 = $, Add, 0, [2, 4, 6, 8, 10]>; +// ^? 30 +type test11 = Expect>; + +type t12 = $, Mul, 1, [2, 4, 6, 8, 10]>; +// ^? 3840 +type test12 = Expect>; + +// @ts-expect-error +type t13 = $, Mul, 1, ["2", "4", "6", "8", "10"]>; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ ❌ + +// @ts-expect-error +type t14 = $; +// ~~~~~~ ❌ + +interface NumToStringReducer extends Fn<[string, number], string> { + return: `${Arg0}${Arg1}`; +} + +interface StringToNumReducer extends Fn<[number, string], number> { + return: NumberImpl.Add, StringImpl.Length>>; +} + +// prettier-ignore +type t15 = $, StringToNumReducer, 1, ["a", "aa", "aaa", "aaaa", "aaaaa"]>; +// ^? 16 +type test15 = Expect>; + +// @ts-expect-error +// prettier-ignore +type t16 = $, NumToStringReducer, 1, ["a", "aa", "aaa", "aaaa", "aaaaa"]>; +// ~~~~~~~~~~~~~~~~~~ ❌ + +interface ToString extends Fn<[number], string> { + return: `${Arg0}`; +} + +interface ToNumber extends Fn<[string], number> { + return: Arg0 extends `${infer N extends number}` ? N : never; +} + +interface Prepend extends Fn<[string, string], string> { + return: `${Arg0}${Arg1}`; +} + +interface ToArray extends Fn<[unknown], [unknown]> { + return: [Arg0]; +} + +type Times10 = $>; + +type t17 = Times10<10>; +// ^? 110 +type test17 = Expect>; + +// @ts-expect-error +type WrongComposition1 = $>; +// ~~~~~~~~~~~~~~ ❌ +// @ts-expect-error +type WrongComposition2 = $>; +// ~~~~~~~~~~~~~~ ❌ + +type Test = $; + +type t18 = Test<"10">; +// ^? 110 +type test18 = Expect>; + +type Sum = $, Add, 0>; + +type TransformNumber = Pipe< + $, + $, + ToString, + $, + ToNumber +>; + +type Composed = Pipe< + $, $>, + $, TransformNumber>, + Sum, + ToString, + $, + ToNumber, + $, + $, + ToString, + $, + ToNumber +>; + +type t19 = $; +// ^? +type test19 = Expect>; diff --git a/src/internals/core/Core3.ts b/src/internals/core/Core3.ts new file mode 100644 index 0000000..da8dcad --- /dev/null +++ b/src/internals/core/Core3.ts @@ -0,0 +1,427 @@ +import { _, unset } from "./Core"; +import { ExcludeUnset, MergeArgs } from "./impl/MergeArgs"; +import * as NumberImpl from "../numbers/impl/numbers"; +import * as StringImpl from "../strings/impl/strings"; +import { Equal, Expect } from "../helpers"; + +/** + * Core + */ + +type Contra = (a: t) => void; + +type GetInputTypes = fn extends { + inputTypes: Contra; +} + ? I + : never; + +type GetOutputType = fn extends { outputType: infer O } ? O : never; + +export interface Fn { + inputTypes: Contra; + outputType: output; + args: unknown; + return: unknown; +} + +export type Apply = (fn & { + args: args; +})["return"]; + +type ExcludePlaceholdersFromInputTypes< + inputTypes extends unknown[], + partialArgs extends unknown[], + result extends unknown[] = [] +> = [inputTypes, partialArgs] extends [ + [infer fInput, ...infer rInput], + [infer fPartial, ...infer rPartial] +] + ? ExcludePlaceholdersFromInputTypes< + rInput, + rPartial, + fPartial extends _ ? [...result, fInput] : result + > + : [...result, ...inputTypes]; + +interface Applied + extends Fn { + name: "$.partial"; + + argsArray: Extract; + allArgs: [...partialArgs, ...this["argsArray"]]; + + inputTypes: Contra< + ExcludePlaceholdersFromInputTypes, partialArgs> + >; + + outputType: fn["outputType"]; + + return: Apply>; +} + +namespace Tuple { + export type Last = xs extends [...any, infer last] ? last : never; +} + +type ApplyLeftToRight = fns extends [ + infer fn extends Fn, + ...infer restFns +] + ? Apply extends infer output + ? output extends $.Error + ? output + : ApplyLeftToRight<[output], restFns> + : never + : args[0]; + +interface Piped extends Fn { + name: "$.pipe"; + + inputTypes: fns[0]["inputTypes"]; + outputType: Extract, Fn>["outputType"]; + + return: ApplyLeftToRight, fns>; +} + +namespace $ { + export type partial< + fn extends Fn, + arg0 extends GetInputTypes[0] | _ = unset, + arg1 extends GetInputTypes[1] | _ = unset, + arg2 extends GetInputTypes[2] | _ = unset, + arg3 extends GetInputTypes[3] | _ = unset + > = Applied>; + + export type call< + fn extends Fn, + arg0 extends GetInputTypes[0] = unset, + arg1 extends GetInputTypes[1] = unset, + arg2 extends GetInputTypes[2] = unset, + arg3 extends GetInputTypes[3] = unset + > = Extract< + (fn & { + args: ExcludeUnset<[arg0, arg1, arg2, arg3]>; + })["return"], + fn["outputType"] + >; + + export type pipe< + fn0 extends Fn, + fn1 extends Fn<[GetOutputType]> | unset = unset, + fn2 extends Fn<[GetOutputType]> | unset = unset, + fn3 extends Fn<[GetOutputType]> | unset = unset, + fn4 extends Fn<[GetOutputType]> | unset = unset, + fn5 extends Fn<[GetOutputType]> | unset = unset, + fn6 extends Fn<[GetOutputType]> | unset = unset, + fn7 extends Fn<[GetOutputType]> | unset = unset, + fn8 extends Fn<[GetOutputType]> | unset = unset, + fn9 extends Fn<[GetOutputType]> | unset = unset, + fn10 extends Fn<[GetOutputType]> | unset = unset, + fn11 extends Fn<[GetOutputType]> | unset = unset, + fn12 extends Fn<[GetOutputType]> | unset = unset, + fn13 extends Fn<[GetOutputType]> | unset = unset, + fn14 extends Fn<[GetOutputType]> | unset = unset, + fn15 extends Fn<[GetOutputType]> | unset = unset, + fn16 extends Fn<[GetOutputType]> | unset = unset, + fn17 extends Fn<[GetOutputType]> | unset = unset, + fn18 extends Fn<[GetOutputType]> | unset = unset, + fn19 extends Fn<[GetOutputType]> | unset = unset, + fn20 extends Fn<[GetOutputType]> | unset = unset + > = Piped< + ExcludeUnset< + [ + fn0, + fn1, + fn2, + fn3, + fn4, + fn5, + fn6, + fn7, + fn8, + fn9, + fn10, + fn11, + fn12, + fn13, + fn14, + fn15, + fn16, + fn17, + fn18, + fn19, + fn20 + ] + > + >; + + const brand = Symbol.for("@hotscript/brand"); + + export interface Error { + [brand]: "@hotscript/error"; + } +} + +export type $< + fn extends Fn, + arg0 extends GetInputTypes[0] = unset, + arg1 extends GetInputTypes[1] = unset, + arg2 extends GetInputTypes[2] = unset, + arg3 extends GetInputTypes[3] = unset +> = Extract< + (fn & { + args: ExcludeUnset<[arg0, arg1, arg2, arg3]>; + })["return"], + fn["outputType"] +>; + +type Args = fn["args"]; +type Arg0 = Extract< + Extract[0], + GetInputTypes[0] +>; +type Arg1 = Extract< + Extract[1], + GetInputTypes[1] +>; +type Arg2 = Extract< + Extract[2], + GetInputTypes[2] +>; +type Arg3 = Extract< + Extract[3], + GetInputTypes[3] +>; + +/** + * Playground 👇 + */ + +type ExpectNumber = [a]; +// arguments are typed internally: +interface TakeNumAndStr extends Fn<[number, string], boolean> { + works: ExpectNumber>; // ✅ + // @ts-expect-error + fails: ExpectNumber>; + // ~~~~~~~~~~ ❌ + return: true; +} + +interface Div extends Fn<[number, number], number> { + return: NumberImpl.Div, Arg1>; +} + +/** + * Full application + */ + +type t1 = $; // 5 +// ^? +type test1 = Expect>; + +// @ts-expect-error +type err1 = $; +// ~~~ ❌ + +// @ts-expect-error +type err2 = $; +// ~~~ ❌ + +/** + * Partial application in order + */ + +type Div1 = $.partial; +type t2 = $; +// ^? +type test2 = Expect>; +// ^? + +// @ts-expect-error +type t3 = $<$.partial, "2">; +// ~~~ ❌ + +/** + * Partial application different order + */ +type DivBy2 = $.partial; +// ^? +type t4 = $; // 5 ✅ +// ^? +type test4 = Expect>; + +type t5 = $<$.partial, 10, 5>; // ✅ +// ^? +type test5 = Expect>; + +type TakeStr = $.partial; +// ^? $.partial + +// @ts-expect-error +type t8 = $; +// ~~ ❌ + +type TakeNum = $.partial; +// ^?$.partial + +type t7 = $; // ✅ +type test7 = Expect>; + +// @ts-expect-error +type t8 = $; +// ~~~~ ❌ + +/** + * Higher order + */ + +interface Map extends Fn<[Fn<[A], B>, A[]], B[]> { + return: Args extends [infer fn extends Fn, infer tuple] + ? { [key in keyof tuple]: $ } + : never; +} + +type t9 = $, $.partial, [2, 4, 6, 8, 10]>; +// ^? [1, 2, 3, 4, 5] +type test9 = Expect>; + +type Defaults = [a] extends [undefined] ? b : a; + +interface Add extends Fn<[number, number], number> { + return: NumberImpl.Add, Arg1>; +} + +interface Mul extends Fn<[number, number], number> { + return: NumberImpl.Mul, Arg1>; +} + +type ReduceImpl = xs extends [ + infer first, + ...infer rest +] + ? ReduceImpl, rest> + : acc; + +interface Reduce extends Fn<[Fn<[B, A], B>, B, A[]], B> { + return: Args extends [infer fn extends Fn, infer acc, infer tuple] + ? ReduceImpl + : never; +} + +type t11 = $, Add, 0, [2, 4, 6, 8, 10]>; +// ^? 30 +type test11 = Expect>; + +type t12 = $, Mul, 1, [2, 4, 6, 8, 10]>; +// ^? 3840 +type test12 = Expect>; + +// @ts-expect-error +type t13 = $, Mul, 1, ["2", "4", "6", "8", "10"]>; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ ❌ + +// @ts-expect-error +type t14 = $; +// ~~~~~~ ❌ + +interface NumToStringReducer extends Fn<[string, number], string> { + return: `${Arg0}${Arg1}`; +} + +interface StringToNumReducer extends Fn<[number, string], number> { + return: NumberImpl.Add, StringImpl.Length>>; +} + +// prettier-ignore +type t15 = $, StringToNumReducer, 1, ["a", "aa", "aaa", "aaaa", "aaaaa"]>; +// ^? 16 +type test15 = Expect>; + +// @ts-expect-error +// prettier-ignore +type t16 = $, NumToStringReducer, 1, ["a", "aa", "aaa", "aaaa", "aaaaa"]>; +// ~~~~~~~~~~~~~~~~~~ ❌ + +interface ToString extends Fn<[number], string> { + return: `${Arg0}`; +} + +interface ToNumber extends Fn<[string], number> { + return: Arg0 extends `${infer N extends number}` ? N : never; +} + +interface Prepend extends Fn<[string, string], string> { + return: `${Arg0}${Arg1}`; +} + +interface ToArray extends Fn<[T], [T]> { + return: [Arg0]; +} + +type Times10 = $>; + +type t17 = Times10<10>; +// ^? 110 +type test17 = Expect>; + +// @ts-expect-error +// prettier-ignore +type WrongComposition1 = $>; +// ~~~~~~~~~~~~~~ ❌ +// @ts-expect-error +type WrongComposition2 = $>; +// ~~~~~~~~~~~~~~ ❌ + +type Test = $; + +type t18 = Test<"10">; +// ^? 110 +type test18 = Expect>; + +type Sum = $.partial; + +type Composed = $.pipe< + $.partial>, + $.partial< + Map, + $.pipe< + $.partial, + $.partial, + ToString, + $.partial, + ToNumber + > + >, + $.partial< + Map, + $.pipe< + $.partial, + $.partial, + ToString, + $.partial, + ToNumber + > + >, + $.partial< + Map, + $.pipe< + $.partial, + $.partial, + ToString, + $.partial, + ToNumber + > + >, + $.partial>, + Sum, + ToString, + $.partial, + ToNumber, + $.partial, + $.partial +>; + +type t19 = $; +// ^? +type test19 = Expect>; diff --git a/src/internals/core/impl/MergeArgs.ts b/src/internals/core/impl/MergeArgs.ts index 2873e13..3c40d53 100644 --- a/src/internals/core/impl/MergeArgs.ts +++ b/src/internals/core/impl/MergeArgs.ts @@ -10,6 +10,15 @@ export type ExcludePlaceholders = xs extends [ : ExcludePlaceholders : output; +export type ExcludeUnset = xs extends [ + infer first, + ...infer rest +] + ? first extends unset + ? ExcludeUnset + : ExcludeUnset + : output; + type MergeArgsRec< pipedArgs extends any[], partialArgs extends any[], diff --git a/test/tuples.test.ts b/test/tuples.test.ts index 6a4a8b3..52fdad8 100644 --- a/test/tuples.test.ts +++ b/test/tuples.test.ts @@ -1,5 +1,5 @@ import { Booleans } from "../src/internals/booleans/Booleans"; -import { Call, Fn, Pipe, _ } from "../src/internals/core/Core"; +import { Call, ComposeLeft, Fn, Pipe, _ } from "../src/internals/core/Core"; import { Equal, Expect } from "../src/internals/helpers"; import { Numbers } from "../src/internals/numbers/Numbers"; import { Strings } from "../src/internals/strings/Strings"; @@ -569,3 +569,35 @@ describe("Tuples", () => { type test3 = Expect>; }); }); + +type Sum = Tuples.Reduce; + +type Comp1 = ComposeLeft< + [ + Tuples.Map>, + Tuples.Map< + ComposeLeft< + [ + Numbers.Add<1>, + Numbers.Mul<3>, + Strings.ToString, + Strings.Prepend<"1">, + Strings.ToNumber + ] + > + >, + Tuples.Map>, + Sum, + Strings.ToString, + Strings.Prepend<"1">, + Strings.ToNumber, + Numbers.Mul<10>, + Numbers.Div<_, 2> + ] +>; + +type Composition = ComposeLeft<[Comp1, Tuples.Append<_, []>, Comp1]>; + +type t19 = Call; +// ^? +type test19 = Expect>;