From 39824143679e3a5ecaca454a9387caf0eaa4527e Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 22 Jun 2024 13:23:19 -0500 Subject: [PATCH 1/2] feat: adds `any.functions`, new API for `some.entryOf`, `some.keyOf`, `some.valueOf` --- src/any/any.ts | 12 +- src/array/array.ts | 2 +- src/exports.ts | 3 +- src/lens/monocle.ts | 2 +- src/match/exports.ts | 1 + src/match/match.ts | 106 ++++++++++++++++ src/number/shared.ts | 2 + src/object/object.ts | 2 +- src/some.ts | 194 ----------------------------- src/some/exports.ts | 1 + src/some/some.ts | 228 +++++++++++++++++++++++++++++++++++ src/type-error/type-error.ts | 4 +- 12 files changed, 356 insertions(+), 201 deletions(-) create mode 100644 src/match/exports.ts create mode 100644 src/match/match.ts delete mode 100644 src/some.ts create mode 100644 src/some/exports.ts create mode 100644 src/some/some.ts diff --git a/src/any/any.ts b/src/any/any.ts index c72a053..9265a04 100644 --- a/src/any/any.ts +++ b/src/any/any.ts @@ -1,12 +1,21 @@ export type { any } export type { ANY_TS_VERSION } from "../version.js" -import type { some } from "../some.js" +import type { some } from "../some/some.js" import type { to } from "../to.js" import type { pathsof } from "../paths/paths.js" import type { ANY_TS_VERSION } from "../version.js" import type { _, id } from "../util.js" +/** + * ## {@link any `any 🧩`} + * `=================` + * + * {@link any `any`} is a namespace for constraints, least upper bounds, + * and type constructors that double as pattern matchers. + * + * It is the main export of the `any-ts` library, and provides its namesake. + */ declare namespace any { export { type ANY_TS_VERSION as VERSION, @@ -127,6 +136,7 @@ declare namespace any { export type strings = any.array> = type export type numbers = any.array> = type export type booleans = any.array> = type + export type functions = any.array> = type export type indices = any.array> = type export type keys = any.array> = type export type paths = any.array> = type diff --git a/src/array/array.ts b/src/array/array.ts index 9f1b3e1..30bbaf5 100644 --- a/src/array/array.ts +++ b/src/array/array.ts @@ -1,6 +1,6 @@ import type { any } from "../any/exports.js" import type { check, typecheck } from "../check/exports.js" -import type { TypeError } from "../exports.js" +import type { TypeError } from "../type-error/type-error.js" import type { nonempty } from "../nonempty/nonempty.js" import type { queue } from "./queue.js" import type { tuple } from "./tuple.js" diff --git a/src/exports.ts b/src/exports.ts index 6239aa8..f530c56 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -9,7 +9,8 @@ export { export type { empty } from "./empty/exports.js" export type { nonempty } from "./nonempty/exports.js" export type { array, nonemptyArray, queue, tuple } from "./array/exports.js" -export type { some } from "./some.js" +export type { some } from "./some/some.js" +export type { match } from "./match/exports.js" export type { object } from "./object/exports.js" export type { boolean } from "./boolean/exports.js" export type { cache } from "./cache/exports.js" diff --git a/src/lens/monocle.ts b/src/lens/monocle.ts index 611412c..71a43ad 100644 --- a/src/lens/monocle.ts +++ b/src/lens/monocle.ts @@ -1,6 +1,6 @@ export type { named } -import type { some } from "../some.js" +import type { some } from "../some/some.js" import type { any } from "../any/exports.js" import type { nonempty } from "../nonempty/nonempty.js" import type { never } from "../exports.js" diff --git a/src/match/exports.ts b/src/match/exports.ts new file mode 100644 index 0000000..8b7e94f --- /dev/null +++ b/src/match/exports.ts @@ -0,0 +1 @@ +export type * as match from "./match.js" diff --git a/src/match/match.ts b/src/match/match.ts new file mode 100644 index 0000000..4b24e06 --- /dev/null +++ b/src/match/match.ts @@ -0,0 +1,106 @@ +import type { any } from "../any/any.js" +import type { never } from "../never/never.js" + +export type { + match_finiteArray as finiteArray, + match_finiteBoolean as finiteBoolean, + match_finiteKey as finiteKey, + match_finiteIndex as finiteIndex, + match_finiteLiteral as finiteLiteral, + match_finiteNumber as finiteNumber, + match_finiteString as finiteString, + +} + +export type { + match_record as record, + match_emptyObject as emptyObject, + match_nonfiniteArray as nonfiniteArray, + match_strict as strict, +} + +declare namespace match_strict { + export { + match_finiteBooleanStrict as finiteBoolean, + match_strictSubsetOf as subsetOf, + } +} + +type match_finiteArray + = [ + t extends any.array + ? number extends t["length"] ? never + : t + : never + ] extends [infer out extends any.array] ? out : never + ; + +type match_nonfiniteArray + = [ + t extends any.array + ? number extends t["length"] ? t + : never + : never + ] extends [infer out] ? out : never + ; + +type match_record + = [t] extends [any.struct] + ? [t] extends [any.array] + ? never + : any.struct + : never + ; + +type match_emptyObject + = [t] extends [any.struct] + ? [keyof t] extends [never] ? {} + : never + : never + ; + +type match_finiteString + = [t] extends [string] + ? [string] extends [t] ? never + : string + : never + ; + +type match_finiteNumber + = [t] extends [number] + ? [number] extends [t] ? never + : number + : never + ; + +type match_finiteBoolean + = [globalThis.Extract] extends [infer out] + ? [boolean] extends [out] ? never + : out + : never + ; + +type match_finiteBooleanStrict + = [[t] extends [boolean] ? [boolean] extends [t] ? never : t : never] extends [infer out] + ? out + : never + ; + +type match_finiteKey + = [t] extends [any.key] + ? any.key extends t ? never + : any.key + : never + ; + +type match_finiteIndex = match_strict.subsetOf + +type match_strictSubsetOf + = set extends set + ? [set extends t ? never : globalThis.Extract] extends [infer out] + ? out + : never.close.inline_var<"out"> + : never.close.distributive<"set"> + ; + +type match_finiteLiteral = match_finiteBoolean | match_strict.subsetOf diff --git a/src/number/shared.ts b/src/number/shared.ts index e832403..1db210c 100644 --- a/src/number/shared.ts +++ b/src/number/shared.ts @@ -13,6 +13,8 @@ export type { import type { any } from "../any/exports.js" +export type parseInt = never | t extends `${infer x extends number}` ? x : t + type isNumber = [x] extends [number] ? true : false type isNegative = [x] extends [any.showable] ? [`${x}`] extends [`-${number}`] ? true : false : false type isPositive = [x] extends [any.showable] ? [`${x}`] extends [`-${number}`] ? false : true : false diff --git a/src/object/object.ts b/src/object/object.ts index f4fa05b..93c5bd4 100644 --- a/src/object/object.ts +++ b/src/object/object.ts @@ -1,5 +1,5 @@ import type { any } from "../any/exports.js" -import type { some } from "../some.js" +import type { some } from "../some/some.js" import type { evaluate } from "../evaluate/exports.js" export declare namespace object { diff --git a/src/some.ts b/src/some.ts deleted file mode 100644 index 342dd49..0000000 --- a/src/some.ts +++ /dev/null @@ -1,194 +0,0 @@ -import type { any } from "./any/exports.js" -import type { never } from "./never/exports.js" -import type { _ } from "./util.js" - -import type { to } from "./to.js" - -export declare namespace some { - export { - /** {@link some.function `some.function`} @external */ - function_ as function, - /** {@link some.class `some.class`} @external */ - class_ as class, - } - - /** @internal Use {@link some.function `some.function`} instead */ - export interface function_ - = any.array, codomain = _> { (...arg: domain): codomain } - - /** @internal Use {@link some.class `some.class`} instead */ - export interface class_< - instance = _, - params extends - | any.array - = any.array, - > { new(...a: params): instance } -} - -/** - * {@link some `some`} is the {@link https://en.wikipedia.org/wiki/Duality_(mathematics) dual} of {@link any `any`} . - * - * **tldr:** - * - * If {@link any `any`} is roughly analogous to - * {@link https://en.wikipedia.org/wiki/Universal_quantification _universal_ quantification} - * (_for all_), then {@link some `some`} corresponds to - * {@link https://en.wikipedia.org/wiki/Existential_quantification _existential_ quantification} - * (_there exists_). - */ -export declare namespace some { - /** {@link unary `some.unary`} @external */ - interface unary< - returns = _, - accepts = any - > { (x: accepts): returns } - - /** {@link binary `some.binary`} @external */ - interface binary< - returns = _, - x = any, - y = any - > { (x: x, y: y): returns } - - /** {@link ternary `some.ternary`} @external */ - interface ternary< - out = _, - a = any, - b = any, - c = any - > { (x: a, y: b, z: c): out } - - /** {@link variadic `some.variadic`} @external */ - interface variadic< - returns = _, - accepts extends any.array = any.array - > { (...args: accepts): returns } - - /** {@link field `some.field`} @external */ - type field = any.field - - /** {@link record `some.record`} @external */ - type record< - key extends - | any.index - = any.key, - value = _ - > = globalThis.Record - - /** {@link predicate `some.predicate`} @external */ - interface predicate { (u: type): boolean } - - /** {@link guard `some.guard`} @external */ - type guard = never | typeguard - - /** {@link typeguard `some.typeguard`} @external */ - type typeguard = never | typePredicate<[source, target]> - - /** @internal Use {@link some.typeguard `some.typeguard`} instead */ - type typePredicate< - map extends - | [source: _, target: _] - = [source: any, target: _] - > = never | ((u: map[0]) => u is map[1]) - - /** {@link asserts `some.asserts`} @external */ - type asserts = never | assertion<[source, target]> - - /** {@link assertion `some.assertion`} @external */ - type assertion< - map extends - | readonly [source: _, target: _] - = readonly [source: any, target: _] - > = never | { (u: map[0]): asserts u is map[1] } - - /** {@link named `some.named`} @external */ - type named< - invariant extends any.field, - type extends - | { [ix in invariant[0]]: invariant[1] } - = { [ix in invariant[0]]: invariant[1] } - > = type - - - /** {@link keyof `some.keyof`} @external */ - type keyof< - invariant, - type extends - | distributive.keyof - = distributive.keyof - > = type - - /** {@link entryOf `some.entryOf`} @external */ - type entryOf< - invariant extends any.object, - type extends - | distributive.entryOf - = distributive.entryOf - > = type - - /** {@link valueOf `some.valueOf`} @external */ - type valueOf< - invariant, - type extends - | distributive.values - = distributive.values - > = type - - /** {@link arrayOf `some.arrayOf`} @external */ - type arrayOf< - invariant, - type extends - | any.array - = any.array - > = type - - /** {@link fieldOf `some.fieldOf`} @external */ - type fieldOf< - invariant, - type extends - | to.entries - = to.entries - > = type - - /** {@link subtypeOf `some.subtypeOf`} @external */ - type subtypeOf< - invariant, - subtype extends - | invariant extends invariant ? invariant : never - = invariant extends invariant ? invariant : never - > = subtype - - type instanceOf - = [distributive] extends [never] - ? [invariant] extends [some.class] ? instance : never - : invariant extends invariant ? some.instanceOf - : never.close.distributive<"invariant"> - ; -} - -export declare namespace distributive { - type values = type extends any.array ? type[number] : type[keyof type] - - type keyof - = ( - type extends any.array - ? Extract - : keyof type - ) extends infer key extends any.index - ? values<{ [ix in key]: ix }> - : never.close.inline_var<"key"> - ; - - type entryOf - = type extends type - ? ( - keyof type extends infer key - ? key extends keyof type - ? readonly [key, type[key]] - : never - : never - ) - : never.close.distributive<"type"> - ; - -} \ No newline at end of file diff --git a/src/some/exports.ts b/src/some/exports.ts new file mode 100644 index 0000000..ece7b95 --- /dev/null +++ b/src/some/exports.ts @@ -0,0 +1 @@ +export type { some } from "./some.js" diff --git a/src/some/some.ts b/src/some/some.ts new file mode 100644 index 0000000..87ba67f --- /dev/null +++ b/src/some/some.ts @@ -0,0 +1,228 @@ +import type { any } from "../any/exports.js" +import type { never } from "../never/exports.js" +import type { _ } from "../util.js" +import type { parseInt } from "../number/shared.js" + +import type { to } from "../to.js" + +/** + * ## {@link some `some 🧩`} + * `=================` + * + * {@link some `some`} is the + * [dual](https://en.wikipedia.org/wiki/Duality_(mathematics)) + * of {@link any `any`}. + * + * If {@link any `any`} is roughly analogous to + * {@link https://en.wikipedia.org/wiki/Universal_quantification _universal_ quantification} + * (_for all_), then {@link some `some`} corresponds to + * {@link https://en.wikipedia.org/wiki/Existential_quantification _existential_ quantification} + * (_there exists_). + */ +export declare namespace some { + export { + /** {@link some_function `some.function`} @external */ + some_function as function, + /** {@link some_class `some.class`} @external */ + some_class as class, + /** {@link some_valueOf `some.valueOf`} @external */ + some_valueOf as valueOf, + /** {@link some_keyof `some.keyof`} @external */ + some_keyof as keyof, + /** {@link some_keyof `some.keyOf`} @external */ + some_keyof as keyOf, + /** {@link some_propertyOf `some.propertyOf`} @external */ + some_propertyOf as propertyOf, + /** {@link some_unary `some.unary`} @external */ + some_unary as unary, + /** {@link some_binary `some.binary`} @external */ + some_binary as binary, + /** {@link some_ternary `some.ternary`} @external */ + some_ternary as ternary, + /** {@link some_variadic `some.variadic`} @external */ + some_variadic as variadic, + /** {@link some_field `some.field`} @external */ + some_field as field, + /** {@link some_record `some.record`} @external */ + some_record as record, + /** {@link some_predicate `some.predicate`} @external */ + some_predicate as predicate, + /** {@link some_guard `some.guard`} @external */ + some_guard as guard, + /** {@link some_typeguard `some.typeguard`} @external */ + some_typeguard as typeguard, + /** {@link some_asserts `some.asserts`} @external */ + some_asserts as asserts, + /** {@link some_assertion `some.assertion`} @external */ + some_assertion as assertion, + /** {@link some_named `some.named`} @external */ + some_named as named, + /** {@link some_entryOf `some.entryOf`} @external */ + some_entryOf as entryOf, + /** {@link some_arrayOf `some.arrayOf`} @external */ + some_arrayOf as arrayOf, + /** {@link some_fieldOf `some.fieldOf`} @external */ + some_fieldOf as fieldOf, + /** {@link some_subtypeOf `some.subtypeOf`} @external */ + some_subtypeOf as subtypeOf, + } +} + +export interface some_function + = any.array, codomain = _> { (...arg: domain): codomain } + +export interface some_class< + instance = _, + params extends + | any.array + = any.array, +> { new(...a: params): instance } + +export type some_keyof< + invariant, + type extends + & keyof invariant + & ([invariant] extends [any.array] + ? [number] extends [invariant["length"]] ? number + : parseInt> + : keyof invariant) + = keyof invariant + & ([invariant] extends [any.array] + ? [number] extends [invariant["length"]] ? number + : parseInt> + : keyof invariant) +> = type + +export type some_valueOf< + invariant, + type extends + | invariant[some_keyof] + = invariant[some_keyof] +> = type + +export type some_propertyOf< + invariant, + type extends + | any.key & keyof invariant + = any.key & keyof invariant +> = type + +export interface some_unary< + returns = _, + accepts = any +> { (x: accepts): returns } + +export interface some_binary< + returns = _, + x = any, + y = any +> { (x: x, y: y): returns } + +export interface some_ternary< + out = _, + a = any, + b = any, + c = any +> { (x: a, y: b, z: c): out } + +export interface some_variadic< + returns = _, + accepts extends any.array = any.array +> { (...args: accepts): returns } + +export type some_field = any.field + +export type some_record< + key extends + | any.index + = any.key, + value = _ +> = globalThis.Record + +export interface some_predicate { (u: type): boolean } + +export type some_guard = never | some_typeguard + +export type some_typeguard = never | some_typePredicate<[source, target]> + +export type some_typePredicate< + map extends + | [source: _, target: _] + = [source: any, target: _] +> = never | ((u: map[0]) => u is map[1]) + +export type some_asserts = never | some_assertion<[source, target]> + +export type some_assertion< + map extends + | readonly [source: _, target: _] + = readonly [source: any, target: _] +> = never | { (u: map[0]): asserts u is map[1] } + +export type some_named< + invariant extends any.field, + type extends + | { [ix in invariant[0]]: invariant[1] } + = { [ix in invariant[0]]: invariant[1] } +> = type + +export type some_entryOf< + invariant extends any.object, + type extends + | any.array> + = any.array> +> = type + +export type some_arrayOf< + invariant, + type extends + | any.array + = any.array +> = type + +export type some_fieldOf< + invariant, + type extends + | to.entries + = to.entries +> = type + +export type some_subtypeOf< + invariant, + subtype extends + | invariant extends invariant ? invariant : never + = invariant extends invariant ? invariant : never +> = subtype + +export type some_instanceOf + = [distributive] extends [never] + ? [invariant] extends [some.class] ? instance : never + : invariant extends invariant ? some_instanceOf + : never.close.distributive<"invariant"> + ; + +export declare namespace distributive { + type values = type extends any.array ? type[number] : type[keyof type] + + type keyof + = ( + type extends any.array + ? Extract + : keyof type + ) extends infer key extends any.index + ? values<{ [ix in key]: ix }> + : never.close.inline_var<"key"> + ; + + type entryOf + = type extends type + ? ( + keyof type extends infer key + ? key extends keyof type + ? readonly [key, type[key]] + : never + : never + ) + : never.close.distributive<"type"> + ; +} diff --git a/src/type-error/type-error.ts b/src/type-error/type-error.ts index 2505b79..ef8eb10 100644 --- a/src/type-error/type-error.ts +++ b/src/type-error/type-error.ts @@ -2,7 +2,7 @@ import { _, id } from "../util.js"; export type { TypeError_ as TypeError } -declare namespace TypeError_ { +export declare namespace TypeError_ { export const URI: "any-ts/TypeError" export type URI = typeof TypeError_.URI @@ -14,7 +14,7 @@ declare namespace TypeError_ { export type TypeError = never | TypeError_<[msg: msg, got: got]> } -interface TypeError_< +export interface TypeError_< error extends | readonly [msg: string, got: _] = readonly [msg: string, got: _] From bf5df0f9967d373c39e0dab416866d77ed1e0d7f Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 22 Jun 2024 13:55:14 -0500 Subject: [PATCH 2/2] chore: commits changeset --- .changeset/grumpy-countries-confess.md | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .changeset/grumpy-countries-confess.md diff --git a/.changeset/grumpy-countries-confess.md b/.changeset/grumpy-countries-confess.md new file mode 100644 index 0000000..958fbd6 --- /dev/null +++ b/.changeset/grumpy-countries-confess.md @@ -0,0 +1,84 @@ +--- +"any-ts": minor +--- + +### new features +- added `match`, a namespace for advanced pattern matching +- added `any.functions` to describe any array of functions + +### breaking changes + +a few members of the `some` namespace behave differently than before: + +- `some.keyOf`: this change was made to support a homomorphic `object.map` function + that operates on both arrays and objects, preserves structure in either case. + + An example implementation: + + ```typescript + /** + * {@link map `map [overload 1/2]`} ("data-last") + * + * [TypeScript playground](https://tsplay.dev/weA2Yw) + * + * {@link map `map`} takes two arguments: + * 1. a function + * 2. a composite data structure that contains one or more targets to apply the function to + * + * A unique feature of this implementation is its polymorphism: it doesn't care whether the + * composite data structure is an array, or whether it's an object. It will apply the argument + * to each of the children, and will preserve the structure of the original shape. + * + * **Trade-off:** the data-last overload of {@link map `map`} is optimized for function composition. + * It works best when used inside a call to {@link fn.pipe `fn.pipe`} or {@link fn.flow `fn.flow`}. + * It comes with greater potential for code re-use, at the cost of slightly slower performance. + * + * **Ergonomics:** if you'd prefer to provide both arguments at the same time, see overload #2. + */ + export function map + (fn: (x: xs[some.keyof], ix: some.keyof, xs: xs) => target): (xs: xs) => { [ix in keyof xs]: target } + /** + * {@link map `map [overload 2/2]`} ("data-first") + * + * [TypeScript playground](https://tsplay.dev/weA2Yw) + * + * {@link map `map`} is a polymorphic function that accepts a function and a data structure (such + * as an array or object) to apply the function to. + * + * A unique feature of this implementation is its ability to abstract away the type of the data + * structure it maps the function over; whether you pass it an object or an array, it will handle + * applying the function to the data strucuture's values and returning a data structure whose type + * corresponds 1-1 with the type of input. + * + * **Trade-off:** the data-first overload of {@link map `map`} evaluates eagerly. It comes with + * slightly better performance than the data-last overload, at the cost of reusability. + * + * **Ergonomics:** if you'd prefer to use {@link map `map`} in a pipeline, see overload #1. + */ + export function map + (xs: xs, fn: (x: xs[some.keyof], xs: xs) => target): { [ix in keyof xs]: target } + // impl. + export function map( + ...args: + | [fn: (x: xs[some.keyof], ix: some.keyof, xs: xs) => target] + | [xs: xs, fn: (x: xs[some.keyof], ix: some.keyof, xs: xs) => target] + ) { + if(args.length === 1) return (xs: xs) => map(xs, args[0]) + else { + const [xs, fn] = args + if(globalThis.Array.isArray(xs)) return xs.map(fn as never) + else { + let out: any.struct = {} + for(const k in xs) + out[k] = fn(xs[k] as never, k as never, xs) + return out + } + } + } + ``` + +- `some.entryOf` + slightly different semantics to support a polymorphic `object.entries` function + +- `some.valueOf` + slightly different semantics to support a polymorphic `object.values` function