From c7afabb88114f8fea6af233cf0627842f691a6c2 Mon Sep 17 00:00:00 2001 From: poteat Date: Tue, 24 Sep 2024 16:53:13 -0700 Subject: [PATCH 01/10] feat: implement Object.Assign utility --- src/object/assign.spec.ts | 29 +++++++++++++++++++++ src/object/assign.ts | 53 +++++++++++++++++++++++++++++++++++++++ src/object/index.ts | 5 ++-- 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/object/assign.spec.ts create mode 100644 src/object/assign.ts diff --git a/src/object/assign.spec.ts b/src/object/assign.spec.ts new file mode 100644 index 000000000..2dfdbdbaa --- /dev/null +++ b/src/object/assign.spec.ts @@ -0,0 +1,29 @@ +import { $, Object, Test } from '..' + +type Assign_Spec = [ + /** + * Can assign a value to a key. + */ + Test.Expect<$<$<$, 'bar'>, {}>, { foo: 'bar' }>, + + /** + * Can assign a value to a key. + */ + Test.Expect<$<$<$, 42>, {}>, { foo: 42 }>, + + /** + * Can overwrite a value. + */ + Test.Expect< + $<$<$, 'bar'>, { foo: 'baz' }>, + { foo: 'bar' } + >, + + /** + * Can assign a value to a key on an object with existing keys. + */ + Test.Expect< + $<$<$, 'bar'>, { bar: 'baz' }>, + { foo: 'bar'; bar: 'baz' } + > +] diff --git a/src/object/assign.ts b/src/object/assign.ts new file mode 100644 index 000000000..4e2843302 --- /dev/null +++ b/src/object/assign.ts @@ -0,0 +1,53 @@ +import { Kind, Type } from '..' + +/** + * `_$assign` is a type-level function that takes in a key `K`, a value `V`, + * and a record `O`, and returns a new record with the key `K` assigned the + * value `V`. + * + * @template {string | number | symbol} K - The key to assign the value to. + * @template {unknown} V - The value to assign to the key. + * @template {Record} O - The record to assign the value to. + * + * @example + * ```ts + * type T0 = _$assign<'foo', 'bar', {}> // { foo: 'bar' } + * type T1 = _$assign<'foo', 42, {}> // { foo: 42 } + * ``` + */ +export type _$assign< + K extends PropertyKey, + V, + O extends Record +> = Omit & { + [key in K]: V +} + +interface Assign_T2 extends Kind.Kind { + f( + x: Type._$cast> + ): _$assign +} + +interface Assign_T1 extends Kind.Kind { + f(x: Type._$cast): Assign_T2 +} + +/** + * `Assign` is a type-level function that takes in a key `K` and a value `V`, + * and a record `O`, and returns a new record with the key `K` assigned the + * value `V`. + * + * @template {string | number | symbol} K - The key to assign the value to. + * @template {unknown} V - The value to assign to the key. + * @template {Record} O - The record to assign the value to. + * + * @example + * ```ts + * type T0 = $<$<$, 'bar'>, {}> // { foo: 'bar' } + * type T1 = $<$<$, 42>, {}> // { foo: 42 } + * ``` + */ +export interface Assign extends Kind.Kind { + f(x: Type._$cast): Assign_T1 +} diff --git a/src/object/index.ts b/src/object/index.ts index b25bc2e18..6e66cfbc2 100644 --- a/src/object/index.ts +++ b/src/object/index.ts @@ -1,6 +1,7 @@ +export * from './assign' +export * from './at' export * from './at-path' export * from './at-path-n' -export * from './at' export * from './deep-input-of' export * from './deep-map-values' export * from './emplace' @@ -9,6 +10,6 @@ export * from './map-keys' export * from './map-values' export * from './merge' export * from './paths' -export * from './values' export * from './update' export * from './update-n' +export * from './values' From db4b1fac58c271594040d3b71c1726ee7b3bf577 Mon Sep 17 00:00:00 2001 From: poteat Date: Tue, 24 Sep 2024 16:53:25 -0700 Subject: [PATCH 02/10] feat: implement countBy util for lists --- src/list/countBy.spec.ts | 36 ++++++++++++++++++++++++ src/list/countBy.ts | 61 ++++++++++++++++++++++++++++++++++++++++ src/list/index.ts | 1 + 3 files changed, 98 insertions(+) create mode 100644 src/list/countBy.spec.ts create mode 100644 src/list/countBy.ts diff --git a/src/list/countBy.spec.ts b/src/list/countBy.spec.ts new file mode 100644 index 000000000..3f5056d72 --- /dev/null +++ b/src/list/countBy.spec.ts @@ -0,0 +1,36 @@ +import { $, Function, List, Test } from '..' + +type CountBy_Spec = [ + /** + * Can count by a predicate. + */ + Test.Expect< + $<$, [1, 1, 1, 2, 3]>, + { + 1: 3 + 2: 1 + 3: 1 + } + >, + + /** + * Can count by a predicate with a constant. + */ + Test.Expect< + $<$>, ['foo', 'foo', 'bar']>, + { + foo: 3 + } + >, + + /** + * Can count by a 'length' predicate over lists. + */ + Test.Expect< + $<$, [[1, 2, 3], [1, 2, 3, 4], [1, 2, 3]]>, + { + 3: 2 + 4: 1 + } + > +] diff --git a/src/list/countBy.ts b/src/list/countBy.ts new file mode 100644 index 000000000..d9a088f0c --- /dev/null +++ b/src/list/countBy.ts @@ -0,0 +1,61 @@ +import { $, Kind, NaturalNumber, Object, Type } from '..' + +type _$countBy2< + F extends Kind.Kind, + T extends unknown[], + O extends Record = {} +> = T extends [infer Head extends Kind._$inputOf, ...infer Tail] + ? _$countBy2< + F, + Tail, + $ extends infer NewKey extends PropertyKey + ? Object._$assign< + NewKey, + NewKey extends keyof O ? NaturalNumber._$increment : 1, + O + > + : never + > + : O + +/** + * `_$countBy` is a type-level function that takes in a kind `K` that returns a + * string, number, or symbol, and a list `T`, and returns a map of the counts + * of the elements in `T` mapped by the result of applying `K` to each element. + * + * @template {Kind} F - The kind that returns a string, number, or symbol. + * @template {unknown[]} T - The list to count the elements of. + * + * @example + * ```ts + * type T0 = _$countBy // { foo: 2, bar: 1 } + * type T1 = _$countBy // { 3: 2, 5: 1 } + * ``` + */ +export type _$countBy< + F extends Kind.Kind, + T extends unknown[], + O extends Record = {} +> = Type._$display<_$countBy2> + +interface CountBy_T extends Kind.Kind { + f(x: Type._$cast): _$countBy +} + +/** + * `CountBy` is a type-level function that takes in a kind `K` that returns a + * string, number, or symbol, and a list `T`, and returns a map of the counts + * of the elements in `T` mapped by the result of applying `K` to each element. + * + * @template {Kind} F - The kind that returns a string, number, or symbol. + * @template {unknown[]} T - The list to count the elements of. + * + * @example + * ```ts + * type T0 = $<$<$, ['foo', 'foo', 'bar']> // { foo: 2, bar: 1 } + * type T1 = $<$<$, ['foo', 'foo', 'quxes']> // { 3: 2, 5: 1 } + * ``` + */ +export interface CountBy extends Kind.Kind { + f(x: Type._$cast): CountBy_T +} diff --git a/src/list/index.ts b/src/list/index.ts index ef7d20d29..eaa5d00ed 100644 --- a/src/list/index.ts +++ b/src/list/index.ts @@ -2,6 +2,7 @@ export * from './accumulate' export * from './at' export * from './chunk' export * from './concat' +export * from './countBy' export * from './duplicates' export * from './every' export * from './filter' From be2020150ea9438198c1691425cc7a032d95d1db Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 12:14:45 -0700 Subject: [PATCH 03/10] feat: implement list max-by utility --- src/list/max-by.spec.ts | 13 +++++++++++ src/list/max-by.ts | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/list/max-by.spec.ts create mode 100644 src/list/max-by.ts diff --git a/src/list/max-by.spec.ts b/src/list/max-by.spec.ts new file mode 100644 index 000000000..9beb15826 --- /dev/null +++ b/src/list/max-by.spec.ts @@ -0,0 +1,13 @@ +import { $, Test, List, Function, String } from 'hkt-toolbelt' + +type MaxBy_Spec = [ + /** + * Can find the maximum element of a list of numbers. + */ + Test.Expect<$<$, [1, 2, 3]>, 3>, + + /** + * Can find the maximum element of a list of strings, based on length. + */ + Test.Expect<$<$, ['foo', 'bars', 'qux']>, 'bars'> +] diff --git a/src/list/max-by.ts b/src/list/max-by.ts new file mode 100644 index 000000000..c88fed594 --- /dev/null +++ b/src/list/max-by.ts @@ -0,0 +1,50 @@ +import { $, Kind, NaturalNumber, Type } from '..' + +/** + * `_$maxBy` is a type-level function that takes in a kind `F` that returns a number, + * and a list `T`, and returns the element in `T` that has the highest score + * when applying `F` to each element. + * + * @template {Kind} F - The kind that returns a number. + * @template {unknown[]} T - The list to find the maximum element of. + * + * @example + * ```ts + * type T0 = _$maxBy // 3 + * ``` + */ +export type _$maxBy< + F extends Kind.Kind, + T extends unknown[], + MaxValue = never, + MaxScore extends number = never +> = T extends [infer Head extends Kind._$inputOf, ...infer Tail] + ? $ extends infer NewScore extends number + ? [MaxScore] extends [never] + ? _$maxBy + : NaturalNumber._$compare extends 1 + ? _$maxBy + : _$maxBy + : never + : MaxValue + +interface MaxBy_T extends Kind.Kind { + f(x: Type._$cast): _$maxBy +} + +/** + * `MaxBy` is a type-level function that takes in a kind `F` that returns a number, + * and a list `T`, and returns the element in `T` that has the highest score + * when applying `F` to each element. + * + * @template {Kind} F - The kind that returns a number. + * @template {unknown[]} T - The list to find the maximum element of. + * + * @example + * ```ts + * type T0 = $<$, [1, 2, 3]> // 3 + * ``` + */ +export interface MaxBy extends Kind.Kind { + f(x: Type._$cast): MaxBy_T +} From 0208fe212e874014a182bedb13afae7ad9db332c Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 12:16:22 -0700 Subject: [PATCH 04/10] feat: improve pipe performance --- src/kind/pipe.ts | 14 +++++++++----- src/list/index.ts | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/kind/pipe.ts b/src/kind/pipe.ts index 5227971b1..cd71268b9 100644 --- a/src/kind/pipe.ts +++ b/src/kind/pipe.ts @@ -1,4 +1,4 @@ -import { Function, Kind, List, Type } from '..' +import { $, Function, Kind, List, Type } from '..' /** * `_$pipe` is a type-level function that allows users to compose @@ -29,10 +29,14 @@ import { Function, Kind, List, Type } from '..' * and `Kind.OutputOf` type-level functions to inspect the input and output * types of the type-level functions that you are piping together. */ -export type _$pipe = Kind._$compose< - List._$reverse, - X -> +export type _$pipe = T extends [ + infer Head extends Kind.Kind, + ...infer Tail extends Kind.Kind[] +] + ? [X] extends [never] + ? never + : _$pipe>>> + : X interface Pipe_T extends Kind.Kind { f( diff --git a/src/list/index.ts b/src/list/index.ts index eaa5d00ed..5c2d546fe 100644 --- a/src/list/index.ts +++ b/src/list/index.ts @@ -23,6 +23,7 @@ export * from './length' export * from './list' export * from './map' export * from './map-n' +export * from './max-by' export * from './pair' export * from './pop' export * from './pop-n' From 3673358c55e345e17b2510ccae0f9b69420bdcbb Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 13:10:50 -0700 Subject: [PATCH 05/10] feat: implement type-level object.entries --- src/object/entries.spec.ts | 10 ++++++++++ src/object/entries.ts | 41 ++++++++++++++++++++++++++++++++++++++ src/object/index.ts | 1 + 3 files changed, 52 insertions(+) create mode 100644 src/object/entries.spec.ts create mode 100644 src/object/entries.ts diff --git a/src/object/entries.spec.ts b/src/object/entries.spec.ts new file mode 100644 index 000000000..d1acfd27a --- /dev/null +++ b/src/object/entries.spec.ts @@ -0,0 +1,10 @@ +import { $, Test, Object } from '..' + +type Entries_Spec = [ + Test.Expect< + $[number], + ['foo', 'bar'] | ['baz', 42] + >, + + Test.Expect<$, []> +] diff --git a/src/object/entries.ts b/src/object/entries.ts new file mode 100644 index 000000000..ba9c5bb33 --- /dev/null +++ b/src/object/entries.ts @@ -0,0 +1,41 @@ +import { Type, Kind, Object } from '..' + +/** + * `_$entries` is a type-level function that takes in a record `O`, and returns + * a list of key-value pairs of the record. + * + * Order is not guaranteed. + * + * @template {Record} O - The record to get the entries of. + * + * @example + * ```ts + * type T0 = _$entries<{ foo: 'bar', baz: 42 }> // [['foo', 'bar'], ['baz', 42]] + * ``` + */ +export type _$entries< + O extends Record, + Keys = Object._$keys +> = { + [I in keyof Keys]: [Keys[I], O[Type._$cast]] +} + +/** + * `Entries` is a type-level function that takes in a record `O`, and returns + * a list of key-value pairs of the record. + * + * Order is not guaranteed. + * + * @template {Record} O - The record to get the entries of. + * + * @example + * ```ts + * type T0 = $ + * // ^? [['foo', 'bar'], ['baz', 42]] + * ``` + */ +export interface Entries extends Kind.Kind { + f( + x: Type._$cast> + ): _$entries +} diff --git a/src/object/index.ts b/src/object/index.ts index 6e66cfbc2..644fae070 100644 --- a/src/object/index.ts +++ b/src/object/index.ts @@ -5,6 +5,7 @@ export * from './at-path-n' export * from './deep-input-of' export * from './deep-map-values' export * from './emplace' +export * from './entries' export * from './keys' export * from './map-keys' export * from './map-values' From b54a5336d925174b7322d66ed663cb4ae1b82073 Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 18:53:11 -0700 Subject: [PATCH 06/10] feat: implement list min-by util --- src/list/index.ts | 1 + src/list/max-by.spec.ts | 2 +- src/list/min-by.spec.ts | 13 +++++++++++ src/list/min-by.ts | 50 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/list/min-by.spec.ts create mode 100644 src/list/min-by.ts diff --git a/src/list/index.ts b/src/list/index.ts index 5c2d546fe..c4ed4328a 100644 --- a/src/list/index.ts +++ b/src/list/index.ts @@ -24,6 +24,7 @@ export * from './list' export * from './map' export * from './map-n' export * from './max-by' +export * from './min-by' export * from './pair' export * from './pop' export * from './pop-n' diff --git a/src/list/max-by.spec.ts b/src/list/max-by.spec.ts index 9beb15826..58e69841f 100644 --- a/src/list/max-by.spec.ts +++ b/src/list/max-by.spec.ts @@ -1,4 +1,4 @@ -import { $, Test, List, Function, String } from 'hkt-toolbelt' +import { $, Test, List, Function, String } from '..' type MaxBy_Spec = [ /** diff --git a/src/list/min-by.spec.ts b/src/list/min-by.spec.ts new file mode 100644 index 000000000..103233793 --- /dev/null +++ b/src/list/min-by.spec.ts @@ -0,0 +1,13 @@ +import { $, Test, List, Function, String } from '..' + +type MinBy_Spec = [ + /** + * Can find the minimum element of a list of numbers. + */ + Test.Expect<$<$, [1, 2, 3]>, 1>, + + /** + * Can find the minimum element of a list of strings, based on length. + */ + Test.Expect<$<$, ['foo', 'bars', 'qu']>, 'qu'> +] diff --git a/src/list/min-by.ts b/src/list/min-by.ts new file mode 100644 index 000000000..8049554c1 --- /dev/null +++ b/src/list/min-by.ts @@ -0,0 +1,50 @@ +import { $, Kind, NaturalNumber, Type } from '..' + +/** + * `_$minBy` is a type-level function that takes in a kind `F` that returns a number, + * and a list `T`, and returns the element in `T` that has the lowest score + * when applying `F` to each element. + * + * @template {Kind} F - The kind that returns a number. + * @template {unknown[]} T - The list to find the minimum element of. + * + * @example + * ```ts + * type T0 = _$minBy // 1 + * ``` + */ +export type _$minBy< + F extends Kind.Kind, + T extends unknown[], + MinValue = never, + MinScore extends number = never +> = T extends [infer Head extends Kind._$inputOf, ...infer Tail] + ? $ extends infer NewScore extends number + ? [MinScore] extends [never] + ? _$minBy + : NaturalNumber._$compare extends -1 + ? _$minBy + : _$minBy + : never + : MinValue + +interface MinBy_T extends Kind.Kind { + f(x: Type._$cast): _$minBy +} + +/** + * `MinBy` is a type-level function that takes in a kind `F` that returns a number, + * and a list `T`, and returns the element in `T` that has the lowest score + * when applying `F` to each element. + * + * @template {Kind} F - The kind that returns a number. + * @template {unknown[]} T - The list to find the minimum element of. + * + * @example + * ```ts + * type T0 = $<$, [1, 2, 3]> // 1 + * ``` + */ +export interface MinBy extends Kind.Kind { + f(x: Type._$cast): MinBy_T +} From 24f466edca7f0cede6d38f08c4e6e69f2e64d49d Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 20:34:57 -0700 Subject: [PATCH 07/10] setup: configure jest to run on .test files only --- jest.config.js | 3 +++ package.json | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index eae75ab8f..431ae11b6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,5 +4,8 @@ module.exports = { transform: { '^.+.tsx?$': ['ts-jest', {}] }, + testMatch: [ + '**/*.test.ts', // Include only 'test' files, excluding type-only spec files + ], passWithNoTests: true } diff --git a/package.json b/package.json index 13b35d187..257090c2f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "hkt-toolbelt", "private": true, "scripts": { - "test": "NODE_OPTIONS=--max-old-space-size=8192 tsc --project tsconfig.spec.json", + "jest": "jest", + "test": "NODE_OPTIONS=--max-old-space-size=8192 tsc --project tsconfig.spec.json && jest", "stress": "NODE_OPTIONS=--max-old-space-size=24576 tsc --project tsconfig.stress.json", "build": "./build.sh", "build:docs": "NODE_OPTIONS=--max-old-space-size=8192 typedoc --skipErrorChecking", From 0138c73b309917f65e91b581b43bfe3dd424514e Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 21:04:02 -0700 Subject: [PATCH 08/10] feat: implement various value-level utils --- src/function/identity.ts | 15 +++++++++++++++ src/kind/pipe.ts | 5 ++++- src/list/countBy.ts | 34 ++++++++++++++++++++++++++++++++-- src/list/first.ts | 15 +++++++++++++++ src/list/last.ts | 15 +++++++++++++++ src/list/max-by.ts | 35 +++++++++++++++++++++++++++++++++-- src/object/entries.ts | 20 ++++++++++++++++++-- 7 files changed, 132 insertions(+), 7 deletions(-) diff --git a/src/function/identity.ts b/src/function/identity.ts index 24fb4da37..b6c6d3fc8 100644 --- a/src/function/identity.ts +++ b/src/function/identity.ts @@ -23,3 +23,18 @@ import { Kind } from '..' export interface Identity extends Kind.Kind { f(x: this[Kind._]): typeof x } + +/** + * Given a value, return the value. + * + * @param {unknown} x - The value to return. + * + * @example + * ```ts + * import { Function } from "hkt-toolbelt"; + * + * const result = Function.identity('foo') + * // ^? foo + * ``` + */ +export const identity = ((x: unknown) => x) as Kind._$reify diff --git a/src/kind/pipe.ts b/src/kind/pipe.ts index cd71268b9..ee9be5e43 100644 --- a/src/kind/pipe.ts +++ b/src/kind/pipe.ts @@ -38,7 +38,7 @@ export type _$pipe = T extends [ : _$pipe>>> : X -interface Pipe_T extends Kind.Kind { +export interface Pipe_T extends Kind.Kind { f( x: Type._$cast< this[Kind._], @@ -88,6 +88,9 @@ export interface Pipe extends Kind.Kind { * Given a list of functions, pipe the functions together, applying them in * order from left to right. * + * @param {Kind.Kind[]} fx - A list of functions to pipe together. + * @param {InputOf} x - The input to pipe through the functions. + * * @example * ```ts * import { Kind, NaturalNumber } from "hkt-toolbelt"; diff --git a/src/list/countBy.ts b/src/list/countBy.ts index d9a088f0c..85a848c1c 100644 --- a/src/list/countBy.ts +++ b/src/list/countBy.ts @@ -1,4 +1,4 @@ -import { $, Kind, NaturalNumber, Object, Type } from '..' +import { $, Kind, NaturalNumber, Object, Type, Function } from '..' type _$countBy2< F extends Kind.Kind, @@ -38,7 +38,7 @@ export type _$countBy< O extends Record = {} > = Type._$display<_$countBy2> -interface CountBy_T extends Kind.Kind { +export interface CountBy_T extends Kind.Kind { f(x: Type._$cast): _$countBy } @@ -59,3 +59,33 @@ interface CountBy_T extends Kind.Kind { export interface CountBy extends Kind.Kind { f(x: Type._$cast): CountBy_T } + +/** + * Given a list, return a new list containing the count of each unique element. + * + * @param {Kind.Kind} f - The function to apply to each element to get the key. + * @param {unknown[]} values - The list to count the elements of. + * + * @example + * ```ts + * import { List, Function } from "hkt-toolbelt"; + * + * const result = List.countBy(Function.identity)(['foo', 'foo', 'bar']) + * // ^? { foo: 2, bar: 1 } + * ``` + */ +export const countBy = ((f: Function.Function) => (values: unknown[]) => { + const result = {} as Record + + for (const element of values) { + const key = f(element as never) as string | number | symbol + + if (key in result) { + result[key]++ + } else { + result[key] = 1 + } + } + + return result +}) as Kind._$reify diff --git a/src/list/first.ts b/src/list/first.ts index e705dc58e..45ac571b4 100644 --- a/src/list/first.ts +++ b/src/list/first.ts @@ -25,3 +25,18 @@ export type _$first = T extends [] ? never : T[0] export interface First extends Kind.Kind { f(x: Type._$cast): _$first } + +/** + * Returns the first element of a list. + * + * @param {unknown[]} x - The list to get the first element of. + * + * @example + * ```ts + * import { List } from "hkt-toolbelt"; + * + * const result = List.first([1, 2, 3]) + * // ^? 1 + * ``` + */ +export const first = ((x: unknown[]) => x[0]) as Kind._$reify diff --git a/src/list/last.ts b/src/list/last.ts index 21daf95c0..1cb18d37a 100644 --- a/src/list/last.ts +++ b/src/list/last.ts @@ -39,3 +39,18 @@ export type _$last = T extends [infer X] export interface Last extends Kind.Kind { f(x: Type._$cast): _$last } + +/** + * Returns the last element of a list. + * + * @param {unknown[]} x - The list to get the last element of. + * + * @example + * ```ts + * import { List } from "hkt-toolbelt"; + * + * const result = List.last([1, 2, 3]) + * // ^? 3 + * ``` + */ +export const last = ((x: unknown[]) => x[x.length - 1]) as Kind._$reify diff --git a/src/list/max-by.ts b/src/list/max-by.ts index c88fed594..7dad2fdf8 100644 --- a/src/list/max-by.ts +++ b/src/list/max-by.ts @@ -1,4 +1,4 @@ -import { $, Kind, NaturalNumber, Type } from '..' +import { $, Kind, NaturalNumber, Type, Function } from '..' /** * `_$maxBy` is a type-level function that takes in a kind `F` that returns a number, @@ -28,7 +28,7 @@ export type _$maxBy< : never : MaxValue -interface MaxBy_T extends Kind.Kind { +export interface MaxBy_T extends Kind.Kind { f(x: Type._$cast): _$maxBy } @@ -48,3 +48,34 @@ interface MaxBy_T extends Kind.Kind { export interface MaxBy extends Kind.Kind { f(x: Type._$cast): MaxBy_T } + +/** + * Given a list, return the element in the list that has the highest score + * when applying `F` to each element. + * + * @param {Kind.Kind} f - The kind that returns a number. + * @param {unknown[]} values - The list to find the maximum element of. + * + * @example + * ```ts + * import { List, Function } from "hkt-toolbelt"; + * + * const result = List.maxBy(Function.identity)([1, 2, 3]) + * // ^? 3 + * ``` + */ +export const maxBy = ((f: Function.Function) => (values: unknown[]) => { + let maxValue = undefined + let maxScore = -Infinity + + for (const value of values) { + const score = f(value as never) as number + + if (score > maxScore) { + maxValue = value + maxScore = score + } + } + + return maxValue +}) as Kind._$reify diff --git a/src/object/entries.ts b/src/object/entries.ts index ba9c5bb33..cae015f99 100644 --- a/src/object/entries.ts +++ b/src/object/entries.ts @@ -1,4 +1,4 @@ -import { Type, Kind, Object } from '..' +import { Type, Kind, Object as Object_ } from '..' /** * `_$entries` is a type-level function that takes in a record `O`, and returns @@ -15,7 +15,7 @@ import { Type, Kind, Object } from '..' */ export type _$entries< O extends Record, - Keys = Object._$keys + Keys = Object_._$keys > = { [I in keyof Keys]: [Keys[I], O[Type._$cast]] } @@ -39,3 +39,19 @@ export interface Entries extends Kind.Kind { x: Type._$cast> ): _$entries } + +/** + * Given a record, return a list of key-value pairs. + * + * @param {Record} x - The record to get the entries of. + * + * @example + * ```ts + * import { Object } from "hkt-toolbelt"; + * + * const result = Object.entries({ foo: 'bar', baz: 42 }) + * // ^? [['foo', 'bar'], ['baz', 42]] + * ``` + */ +export const entries = ((x: Record) => + Object.entries(x)) as Kind._$reify From 4d2420e60430ee3fbe373e22d951e44890cc26d0 Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 21:04:56 -0700 Subject: [PATCH 09/10] feat: rename runtime tests from .spec to .test --- src/conditional/{equals.spec.ts => equals.test.ts} | 0 src/function/{identity.spec.ts => identity.test.ts} | 4 ++++ src/kind/{pipe.spec.ts => pipe.test.ts} | 0 src/list/{countBy.spec.ts => countBy.test.ts} | 7 +++++++ src/list/{duplicates.spec.ts => duplicates.test.ts} | 0 src/list/{first.spec.ts => first.test.ts} | 4 ++++ src/list/{last.spec.ts => last.test.ts} | 4 ++++ src/list/{length.spec.ts => length.test.ts} | 0 src/list/{max-by.spec.ts => max-by.test.ts} | 4 ++++ .../{increment.spec.ts => increment.test.ts} | 0 src/object/{entries.spec.ts => entries.test.ts} | 7 +++++++ tsconfig.spec.json | 3 ++- 12 files changed, 32 insertions(+), 1 deletion(-) rename src/conditional/{equals.spec.ts => equals.test.ts} (100%) rename src/function/{identity.spec.ts => identity.test.ts} (83%) rename src/kind/{pipe.spec.ts => pipe.test.ts} (100%) rename src/list/{countBy.spec.ts => countBy.test.ts} (78%) rename src/list/{duplicates.spec.ts => duplicates.test.ts} (100%) rename src/list/{first.spec.ts => first.test.ts} (83%) rename src/list/{last.spec.ts => last.test.ts} (90%) rename src/list/{length.spec.ts => length.test.ts} (100%) rename src/list/{max-by.spec.ts => max-by.test.ts} (72%) rename src/natural-number/{increment.spec.ts => increment.test.ts} (100%) rename src/object/{entries.spec.ts => entries.test.ts} (57%) diff --git a/src/conditional/equals.spec.ts b/src/conditional/equals.test.ts similarity index 100% rename from src/conditional/equals.spec.ts rename to src/conditional/equals.test.ts diff --git a/src/function/identity.spec.ts b/src/function/identity.test.ts similarity index 83% rename from src/function/identity.spec.ts rename to src/function/identity.test.ts index bc3151197..9a17d2b52 100644 --- a/src/function/identity.spec.ts +++ b/src/function/identity.test.ts @@ -19,3 +19,7 @@ type Identity_Spec = [ */ Test.Expect<$, Function.Identity> ] + +it('should return the input', () => { + expect(Function.identity('foo')).toBe('foo') +}) diff --git a/src/kind/pipe.spec.ts b/src/kind/pipe.test.ts similarity index 100% rename from src/kind/pipe.spec.ts rename to src/kind/pipe.test.ts diff --git a/src/list/countBy.spec.ts b/src/list/countBy.test.ts similarity index 78% rename from src/list/countBy.spec.ts rename to src/list/countBy.test.ts index 3f5056d72..a9abc9164 100644 --- a/src/list/countBy.spec.ts +++ b/src/list/countBy.test.ts @@ -34,3 +34,10 @@ type CountBy_Spec = [ } > ] + +it('should return the count of each unique element', () => { + expect(List.countBy(Function.identity)(['foo', 'foo', 'bar'])).toEqual({ + foo: 2, + bar: 1 + }) +}) diff --git a/src/list/duplicates.spec.ts b/src/list/duplicates.test.ts similarity index 100% rename from src/list/duplicates.spec.ts rename to src/list/duplicates.test.ts diff --git a/src/list/first.spec.ts b/src/list/first.test.ts similarity index 83% rename from src/list/first.spec.ts rename to src/list/first.test.ts index acbbc49ed..8808ad714 100644 --- a/src/list/first.spec.ts +++ b/src/list/first.test.ts @@ -22,3 +22,7 @@ type First_Spec = [ // @ts-expect-error $ ] + +it('should return the first element of a list', () => { + expect(List.first([1, 2, 3])).toBe(1) +}) diff --git a/src/list/last.spec.ts b/src/list/last.test.ts similarity index 90% rename from src/list/last.spec.ts rename to src/list/last.test.ts index b895122c4..19619e707 100644 --- a/src/list/last.spec.ts +++ b/src/list/last.test.ts @@ -33,3 +33,7 @@ type Last_Spec = [ */ Test.Expect<$, string> ] + +it('should return the last element of a list', () => { + expect(List.last([1, 2, 3])).toBe(3) +}) diff --git a/src/list/length.spec.ts b/src/list/length.test.ts similarity index 100% rename from src/list/length.spec.ts rename to src/list/length.test.ts diff --git a/src/list/max-by.spec.ts b/src/list/max-by.test.ts similarity index 72% rename from src/list/max-by.spec.ts rename to src/list/max-by.test.ts index 58e69841f..3b4f8c545 100644 --- a/src/list/max-by.spec.ts +++ b/src/list/max-by.test.ts @@ -11,3 +11,7 @@ type MaxBy_Spec = [ */ Test.Expect<$<$, ['foo', 'bars', 'qux']>, 'bars'> ] + +it('should return the element in the list that has the highest score', () => { + expect(List.maxBy(Function.identity)([1, 2, 3])).toBe(3) +}) diff --git a/src/natural-number/increment.spec.ts b/src/natural-number/increment.test.ts similarity index 100% rename from src/natural-number/increment.spec.ts rename to src/natural-number/increment.test.ts diff --git a/src/object/entries.spec.ts b/src/object/entries.test.ts similarity index 57% rename from src/object/entries.spec.ts rename to src/object/entries.test.ts index d1acfd27a..e7b89225a 100644 --- a/src/object/entries.spec.ts +++ b/src/object/entries.test.ts @@ -8,3 +8,10 @@ type Entries_Spec = [ Test.Expect<$, []> ] + +it('should return the entries of an object', () => { + expect(Object.entries({ foo: 'bar', baz: 42 })).toEqual([ + ['foo', 'bar'], + ['baz', 42] + ]) +}) diff --git a/tsconfig.spec.json b/tsconfig.spec.json index e7f96b2bd..739a2f910 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -21,7 +21,8 @@ "strict": true }, "include": [ - "src/**/*.spec.ts" + "src/**/*.spec.ts", + "src/**/*.test.ts" ], "exclude": [ "src/**/*.stress.spec.ts", From ad3b8592ef16d99cb8ad6f092c751e6e14637f1c Mon Sep 17 00:00:00 2001 From: poteat Date: Sat, 28 Sep 2024 21:05:14 -0700 Subject: [PATCH 10/10] feat: disable publish-docs github step --- .github/workflows/publish-docs.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/publish-docs.yml diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml deleted file mode 100644 index a0c4018bf..000000000 --- a/.github/workflows/publish-docs.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Publish docs to GitHub Pages - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - publish-docs-to-gh-pages: - name: Publish docs to GitHub Pages - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout the repository - uses: actions/checkout@v3 - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - - name: Install npm dependencies - run: npm install - - name: Run build script - run: npm run build:docs - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./typedoc \ No newline at end of file