Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide new list methods, as well as a much-needed Kind.LazyPipe operator #89

Merged
merged 10 commits into from
Sep 29, 2024
10 changes: 10 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## [0.24.3]

- Add `List.IsEmpty` to check if a list is empty.
- Add `List.Take` to take the first N elements of a list.
- Add `List.TransformAt` to transform the element at a given index in a list.
- Add `List.UnshiftValue` to unshift a value onto a list.
- Add `Kind.LazyPipe` to apply a list of kinds lazily, i.e. allow intermediate currying.
- Add `Boolean.ToNumber` to convert a boolean to a number, i.e. 1 or 0.
- Modify `List.At` to return the union of all elements in a list if given `number` as an index.

## [0.24.2]

- Fix `List.CountBy` to gracefully handle variadics.
Expand Down
1 change: 1 addition & 0 deletions src/boolean/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './and'
export * from './nand'
export * from './not'
export * from './or'
export * from './to-number'
export * from './nor'
export * from './xor'
export * from './xnor'
Expand Down
18 changes: 18 additions & 0 deletions src/boolean/to-number.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { $, Boolean, Test } from '..'

type ToNumber_Spec = [
/**
* Can convert true to 1.
*/
Test.Expect<$<Boolean.ToNumber, true>, 1>,

/**
* Can convert false to 0.
*/
Test.Expect<$<Boolean.ToNumber, false>, 0>,

/**
* 'boolean' results in 0 | 1.
*/
Test.Expect<$<Boolean.ToNumber, boolean>, 0 | 1>
]
31 changes: 31 additions & 0 deletions src/boolean/to-number.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Kind, Type } from '..'

/**
* `_$toNumber` is a type-level function that takes in a boolean `B` and returns
* either `1` or `0`.
*
* @template {boolean} B - The boolean to convert to a number.
*
* @example
* ```ts
* type T0 = _$toNumber<true> // 1
* type T1 = _$toNumber<false> // 0
* ```
*/
export type _$toNumber<B extends boolean> = B extends true ? 1 : 0

/**
* `ToNumber` is a type-level function that takes in a boolean `B` and returns
* either `1` or `0`.
*
* @template {boolean} B - The boolean to convert to a number.
*
* @example
* ```ts
* type T0 = $<Boolean.ToNumber, true> // 1
* type T1 = $<Boolean.ToNumber, false> // 0
* ```
*/
export interface ToNumber extends Kind.Kind {
f(x: Type._$cast<this[Kind._], boolean>): _$toNumber<typeof x>
}
1 change: 1 addition & 0 deletions src/kind/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './curry'
export * from './input-of'
export * from './juxt'
export * from './kind'
export * from './lazy-pipe'
export * from './output-of'
export * from './parameters'
export * from './pipe'
Expand Down
28 changes: 28 additions & 0 deletions src/kind/lazy-pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { $, Combinator, Kind, List, String, Test } from '..'

type LazyPipe_Spec = [
/**
* Can pipe simple operations.
*/
Test.Expect<
$<
$<Kind.LazyPipe, [$<String.Append, 'bar'>, $<String.Append, 'foo'>]>,
'baz'
>,
'bazbarfoo'
>,

/**
* Can pipe operations of differing arity.
*/
Test.Expect<
$<
$<
$<Kind.LazyPipe, [$<Combinator.Collate, 2>, List.Flatten, List.Length]>,
[1, 2, 3]
>,
[4, 5, 6]
>,
6
>
]
54 changes: 54 additions & 0 deletions src/kind/lazy-pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { $, Kind, Type } from '..'

/**
* Given a list of kinds, apply them in order to a value.
*
* This is similar to `Kind.Pipe`, but if the result of a kind is a kind, we return
* a new pipe instead of sending the result to the next kind.
*
* @template {Kind.Kind[]} KX - A list of kinds to apply.
* @template {unknown} X - The value to apply the kinds to.
*
* @example
* ```ts
* import { $, Kind, String } from "hkt-toolbelt";
*
* type Result = $<$<Kind.LazyPipe, [String.ToUpper, String.ToLower]>, "FOO">; // "foo"
* ```
*/
export type _$lazyPipe<T extends Kind.Kind[], X> = T extends [
infer Head extends Kind.Kind,
...infer Tail extends Kind.Kind[]
]
? [X] extends [never]
? never
: $<Head, Type._$cast<X, Kind._$inputOf<Head>>> extends infer Result
? Result extends Kind.Kind
? LazyPipe_T<[Result, ...Tail]>
: _$lazyPipe<Tail, Result>
: never
: X

interface LazyPipe_T<KX extends Kind.Kind[]> extends Kind.Kind {
f(x: Type._$cast<this[Kind._], unknown>): _$lazyPipe<KX, typeof x>
}

/**
* Given a list of kinds, apply them in order to a value.
*
* This is similar to `Kind.Pipe`, but if the result of a kind is a kind, we return
* a new pipe instead of sending the result to the next kind.
*
* @template {Kind.Kind[]} KX - A list of kinds to apply.
* @template {unknown} X - The value to apply the kinds to.
*
* @example
* ```ts
* import { $, Kind, String } from "hkt-toolbelt";
*
* type Result = $<$<Kind.LazyPipe, [String.ToUpper, String.ToLower]>, "FOO">; // "foo"
* ```
*/
export interface LazyPipe extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Kind.Kind[]>): LazyPipe_T<typeof x>
}
9 changes: 8 additions & 1 deletion src/list/at.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,12 @@ type At_Spec = [
* Emits an error if being applied to a non-tuple.
*/
// @ts-expect-error
$<$<List.At, 1>, number>
$<$<List.At, 1>, number>,

/**
* Accessing via a number type returns the union of all elements.
*/
Test.Expect<$<$<List.At, number>, ['a', 'b', 'c']>, 'a' | 'b' | 'c'>
]

type X = $<$<List.At, number>, ['a', 'b', 'c']>
71 changes: 39 additions & 32 deletions src/list/at.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,6 @@
import { DigitList, Kind, Type, Number, List, NaturalNumber } from '../'

/**
* `_$at` is a type-level function that retrieves and returns an element from a tuple type.
*
* It takes in two arguments: a tuple, and an integer specifying the index of the element to be accessed.
* Both positive and negative indices are supported, with negative indices being normalized into zero-based indices under the hood.
*
* @template T - A tuple type.
* @template POS - An integer type specifying the index of the element to be accessed.
* @returns The element of `T` at index `POS`.
*
* ## Edge Cases
*
* If `POS` is greater than or equal to the length of `T`, returns `never`.
* If `POS` is lesser than the negated length of `T`, returns `never`.
* If `POS` is not a numeric type, returns `never`.
*
* @example
* A negative index counts back from the end of the input tuple.
*
* type MyList = ['a', 'b', 'c', 'd', 'e'];
*
* type Head = List._$at<MyList, 0>; // 'a'
* type Tail = List._$at<MyList, -1>; // 'e'
*
* type IsNever = List._$at<MyList, 5>; // never
* type IsNever2 = List._$at<MyList, -6>; // never
* ```
*/
export type _$at<
type _$at2<
/**
* The list to extract the element from.
*/
Expand Down Expand Up @@ -63,10 +35,45 @@ export type _$at<
INDEX extends number = DigitList._$toNumber<POS_NORM>
> = POS_NORM extends never ? never : T[INDEX]

/**
* `_$at` is a type-level function that retrieves and returns an element from a tuple type.
*
* It takes in two arguments: a tuple, and an integer specifying the index of the element to be accessed.
* Both positive and negative indices are supported, with negative indices being normalized into zero-based indices under the hood.
*
* @template T - A tuple type.
* @template POS - An integer type specifying the index of the element to be accessed.
* @returns The element of `T` at index `POS`.
*
* ## Edge Cases
*
* If `POS` is greater than or equal to the length of `T`, returns `never`.
* If `POS` is lesser than the negated length of `T`, returns `never`.
* If `POS` is not a numeric type, returns `never`.
*
* @example
* A negative index counts back from the end of the input tuple.
*
* type MyList = ['a', 'b', 'c', 'd', 'e'];
*
* type Head = List._$at<MyList, 0>; // 'a'
* type Tail = List._$at<MyList, -1>; // 'e'
*
* type IsNever = List._$at<MyList, 5>; // never
* type IsNever2 = List._$at<MyList, -6>; // never
* ```
*/
export type _$at<
T extends unknown[],
POS extends Number.Number
> = number extends POS
? T[number]
: Number._$isInteger<POS> extends true
? _$at2<T, POS>
: never

interface At_T<X extends Number.Number> extends Kind.Kind {
f(
x: Type._$cast<this[Kind._], unknown[]>
): Number._$isInteger<X> extends true ? _$at<typeof x, X> : never
f(x: Type._$cast<this[Kind._], unknown[]>): _$at<typeof x, X>
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/list/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './includesValue'
export * from './intersect'
export * from './inverse-map'
export * from './inverse-map-n'
export * from './is-empty'
export * from './is-variadic'
export * from './iterate'
export * from './last'
Expand All @@ -40,7 +41,10 @@ export * from './shift-n'
export * from './slice'
export * from './some'
export * from './splice'
export * from './take'
export * from './times'
export * from './transform-at'
export * from './unique'
export * from './unshift-value'
export * from './unshift'
export * from './zip'
13 changes: 13 additions & 0 deletions src/list/is-empty.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { $, List, Test } from '..'

type IsEmpty_Spec = [
/**
* Can check if a list is empty.
*/
Test.Expect<$<List.IsEmpty, []>, true>,

/**
* Can check if a list is not empty.
*/
Test.Expect<$<List.IsEmpty, [1, 2, 3]>, false>
]
29 changes: 29 additions & 0 deletions src/list/is-empty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Kind, Type } from '..'

/**
* `_$isEmpty` is a type-level function that checks if a list is empty.
*
* @template T - The list to check.
*
* @example
* ```ts
* type T0 = List._$isEmpty<[]> // true
* type T1 = List._$isEmpty<[1, 2, 3]> // false
* ```
*/
export type _$isEmpty<T extends unknown[]> = T extends [] ? true : false

/**
* `IsEmpty` is a type-level function that checks if a list is empty.
*
* @template T - The list to check.
*
* @example
* ```ts
* type T0 = $<$<List.IsEmpty, []>> // true
* type T1 = $<$<List.IsEmpty, [1, 2, 3]>> // false
* ```
*/
export interface IsEmpty extends Kind.Kind {
f(x: Type._$cast<this[Kind._], unknown[]>): _$isEmpty<typeof x>
}
23 changes: 23 additions & 0 deletions src/list/take.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { $, List, Test } from '..'

type Take_Spec = [
/**
* Can take the first two elements of a list.
*/
Test.Expect<$<$<List.Take, 2>, [1, 2, 3]>, [1, 2]>,

/**
* Can take the first three elements of a list.
*/
Test.Expect<$<$<List.Take, 3>, [1, 2, 3]>, [1, 2, 3]>,

/**
* Can take zero elements from a list.
*/
Test.Expect<$<$<List.Take, 0>, [1, 2, 3]>, []>,

/**
* Taking more elements than the list contains returns never.
*/
Test.Expect<$<$<List.Take, 4>, [1, 2, 3]>, never>
]
45 changes: 45 additions & 0 deletions src/list/take.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Kind, Type, Number } from '..'

/**
* `_$take` is a type-level function that takes in a number `N` and a list `T`,
* and returns a list of the first `N` elements of `T`.
*
* @template {number} N - The number of elements to take.
* @template {unknown[]} T - The list to take elements from.
*
* @example
* ```ts
* type T0 = _$take<2, [1, 2, 3]> // [1, 2]
* ```
*/
export type _$take<
N extends Number.Number,
T extends unknown[],
O extends unknown[] = []
> = 0 extends 1
? never
: O['length'] extends N
? O
: T extends [infer Head, ...infer Tail]
? _$take<N, Tail, [...O, Head]>
: never

interface Take_T<N extends Number.Number> extends Kind.Kind {
f(x: Type._$cast<this[Kind._], unknown[]>): _$take<N, typeof x>
}

/**
* `Take` is a type-level function that takes in a number `N` and a list `T`,
* and returns a list of the first `N` elements of `T`.
*
* @template {number} N - The number of elements to take.
* @template {unknown[]} T - The list to take elements from.
*
* @example
* ```ts
* type T0 = $<$<List.Take, 2>, [1, 2, 3]> // [1, 2]
* ```
*/
export interface Take extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Number.Number>): Take_T<typeof x>
}
Loading
Loading