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

Add additional reified list utilities #97

Merged
merged 30 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1bc112b
feat: reify string to-list method
poteat Oct 12, 2024
aca9a90
feat: add find-index list method
poteat Oct 12, 2024
14a4111
fix: make includes-value filename consistent
poteat Oct 12, 2024
4844454
feat: add list index-of utility
poteat Oct 12, 2024
ff87b43
docs: fix index-of example
poteat Oct 12, 2024
04b26f2
feat: add starts-with list util
poteat Oct 12, 2024
7f71fe6
feat: add ends-with list util
poteat Oct 12, 2024
b58b71f
feat: add sequence-search utility
poteat Oct 12, 2024
52fa5b4
fix: use deep equality for value search in lists
poteat Oct 12, 2024
5b851f2
feat: add list replace function
poteat Oct 12, 2024
2d5ef9d
feat: reify list remove utility
poteat Oct 12, 2024
160e706
feat: optimize list remove inference depth
poteat Oct 12, 2024
32aca20
feat: add subsequence replacement utility
poteat Oct 12, 2024
5a44412
feat: make sequence replacement total (all instances)
poteat Oct 12, 2024
6a1a293
feat: add sequence removal utility
poteat Oct 12, 2024
7bc0e8e
fix: clamp nat num decrement to zero on runtime level
poteat Oct 12, 2024
ad715e4
docs: fix import in example for fix-seq method
poteat Oct 12, 2024
b09dd16
feat: add fixed point combinator
poteat Oct 12, 2024
b5e2591
feat: reify min-by utility
poteat Oct 13, 2024
b351e23
fix: filename casing for list count-by
poteat Oct 13, 2024
5cdfaa3
fix: filename casing for list count-by
poteat Oct 13, 2024
b06dbe7
feat: add max-index-by list utility
poteat Oct 13, 2024
9b37468
feat: add min-index-by list utility
poteat Oct 13, 2024
333bc87
fix: max/min index-by reifications
poteat Oct 13, 2024
6932059
feat: add list remove-index utility
poteat Oct 13, 2024
52b3f19
feat: reify list reduce method
poteat Oct 13, 2024
c58c0d9
fix: type issue in index-of-seq
poteat Oct 13, 2024
547e1fc
chore: add changelog for 0.24.11
poteat Oct 13, 2024
871d1ba
feat: reify List some utility
poteat Oct 13, 2024
93196ab
feat: reify List find method
poteat Oct 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## [0.24.11]

- Reify `String.ToList` to a value-level function.
- Add `List.FindIndex` to find the index of a value in a list that satisfies a predicate.
- Add `List.IndexOf` to find the index of a value in a list.
- Add `List.StartsWith` to check if a list starts with a sequence of values.
- Add `List.EndsWith` to check if a list ends with a sequence of values.
- Add `List.IndexOfSequence` to find the index of a sequence of values in a list.
- Fix runtime list search utilities to search via deep equality.
- Add `List.Replace` to replace all instances of a value in a list with another value.
- Add `List.Remove` to remove all instances of a value from a list.
- Add `List.ReplaceSequence` to replace all instances of a sequence of values in a list with another sequence.
- Add `List.RemoveSequence` to remove all instances of a sequence of values from a list.
- Fix `NaturalNumber.decrement` to return zero when decrementing zero during runtime.
- Add `Combinator.Fix` to find a fixed point of a higher-order type.
- Reify `List.MinBy` to a value-level function.
- Add `List.MaxIndexBy` to find the index of the maximum element in a list according to a scoring function.
- Add `List.MinIndexBy` to find the index of the minimum element in a list according to a scoring function.
- Add `List.RemoveIndex` to remove an element at a specified index from a list.
- Reify `List.Reduce` to a value-level function.
- Reify `List.Some` to a value-level function.
- Reify `List.Find` to a value-level function.

## [0.24.10]

- Add `String.CamelCase` to convert a string to camelCase.
Expand Down
4 changes: 2 additions & 2 deletions src/combinator/fix-sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,11 @@ export interface FixSequence extends Kind.Kind {
*
* @example
* ```ts
* import { Kind, NaturalNumber } from "hkt-toolbelt";
* import { Combinator, NaturalNumber } from "hkt-toolbelt";
*
* const myFcn = // decrement by 1 if above 0, otherwise return 0
*
* const result = Kind.fixSequence(myFcn)(100)
* const result = Combinator.fixSequence(myFcn)(100)
* // ^? 0
* ```
*/
Expand Down
25 changes: 25 additions & 0 deletions src/combinator/fix.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { $, Combinator, Test, NaturalNumber, String } from '..'

type Rewrite = $<Combinator.Fix, $<$<String.Replace, 'xyz'>, 'x'>>

const rewrite = Combinator.fix(String.replace('xyz')('x'))

type Fix_Spec = [
/**
* Can find a fixed point.
*/
Test.Expect<$<$<Combinator.Fix, NaturalNumber.Decrement>, 100>, 0>,

/**
* Can execute a term rewriting system.
*/
Test.Expect<$<Rewrite, 'zyxyzyzyz'>, 'zyx'>
]

it('should find a fixed point', () => {
expect(Combinator.fix(NaturalNumber.decrement)(100)).toBe(0)
})

it('can execute a term rewriting system', () => {
expect(Combinator.fix(rewrite)('zyxyzyzyz')).toBe('zyx')
})
74 changes: 74 additions & 0 deletions src/combinator/fix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { $, Kind, Type, Conditional, Function } from '..'
import { deepEqual } from '../_internal/deepEqual'

/**
* `_$fix` is a type-level function that takes in a kind `K` and a value `X`,
* and returns a new value that is the result of applying `K` to `X` until it
* no longer returns a kind.
*
* @template {Kind.Kind} K - The kind to fix.
* @template {unknown} X - The value to fix.
*
* @example
* ```ts
* import { Combinator, NaturalNumber } from "hkt-toolbelt";

* type Result = Combinator._$fix<NaturalNumber.Decrement, 100>
* // ^? 0
* ```
*/
export type _$fix<
K extends Kind.Kind,
X,
NEXT_VALUE = $<K, Type._$cast<X, Kind._$inputOf<K>>>
> = Conditional._$equals<NEXT_VALUE, X> extends true ? X : _$fix<K, NEXT_VALUE>

interface Fix_T<K extends Kind.Kind> extends Kind.Kind {
f(x: this[Kind._]): _$fix<K, typeof x>
}

/**
* `Fix` is a type-level function that takes in a kind `K` and a value `X`,
* and returns a new value that is the result of applying `K` to `X` until it
* no longer returns a kind.
*
* @template {Kind.Kind} K - The kind to fix.
* @template {unknown} X - The value to fix.
*
* @example
* ```ts
* import { Combinator, NaturalNumber } from "hkt-toolbelt";
*
* type Result = $<$<Combinator.Fix, NaturalNumber.Decrement>, 100>
* // ^? 0
* ```
*/
export interface Fix extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Kind.Kind>): Fix_T<typeof x>
}

/**
* Given a higher-order type and a value, loop until a fixed point is found.
*
* @param {Kind.Kind} f - The kind for which the fixed-point sequence is calculated.
* @param {unknown} x - The initial value to start the loop with.
*
* @example
* ```ts
* import { Combinator, NaturalNumber } from "hkt-toolbelt";
*
* const result = Combinator.fix(NaturalNumber.decrement)(100)
* // ^? 0
* ```
*/
export const fix = ((f: Function.Function) => (x: unknown) => {
let value = x
let prevValue = x

do {
prevValue = value
value = f(value as never)
} while (!deepEqual(prevValue, value))

return value
}) as unknown as Kind._$reify<Fix>
1 change: 1 addition & 0 deletions src/combinator/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './apply-self'
export * from './collate'
export * from './fix-sequence'
export * from './fix'
export * from './recursive-kind'
export * from './self'
File renamed without changes.
File renamed without changes.
30 changes: 30 additions & 0 deletions src/list/ends-with.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { $, List, Test } from '..'

type EndsWith_Spec = [
/**
* Can check if a list ends with a value.
*/
Test.Expect<$<$<List.EndsWith, [3, 4, 5]>, [1, 2, 3, 4, 5]>, true>,

/**
* Can check if a list does not end with a value.
*/
Test.Expect<$<$<List.EndsWith, [3, 4, 5]>, [1, 2, 3, 4]>, false>,

/**
* Can check if a list ends with an empty value.
*/
Test.Expect<$<$<List.EndsWith, []>, [1, 2, 3]>, true>
]

it('should return true if the list ends with the value', () => {
expect(List.endsWith([3, 4, 5])([1, 2, 3, 4, 5])).toBe(true)
})

it('should return false if the list does not end with the value', () => {
expect(List.endsWith([3, 4, 5])([1, 2, 3, 4])).toBe(false)
})

it('should return true if the list ends with an empty value', () => {
expect(List.endsWith([])([1, 2, 3])).toBe(true)
})
81 changes: 81 additions & 0 deletions src/list/ends-with.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Kind, Type, Conditional } from '..'

/**
* `_$endsWith` is a type-level function that takes in a list `X` and a list
* `T`, and returns a boolean indicating whether `T` ends with `X`.
*
* @template X - The value to check.
* @template T - The list to check.
*
* @returns A boolean indicating whether `T` ends with `X`.
*
* @example
* For example, we can use `_$endsWith` to check if a list ends with a value:
*
* ```ts
* import { List } from "hkt-toolbelt";
*
* type Result = List._$endsWith<[3, 4, 5], [1, 2, 3, 4, 5]>; // true
* ```
*/
export type _$endsWith<X extends unknown[], T extends unknown[]> = X extends [
...infer TailX,
infer HeadX
]
? T extends [...infer TailT, infer HeadT]
? Conditional._$equals<HeadX, HeadT> extends true
? _$endsWith<TailX, TailT>
: false
: false
: true

interface EndsWith_T<X extends unknown[]> extends Kind.Kind {
f(x: Type._$cast<this[Kind._], unknown[]>): _$endsWith<X, typeof x>
}

/**
* `EndsWith` is a type-level function that takes in a list `X` and a list
* `T`, and returns a boolean indicating whether `T` ends with `X`.
*
* @template X - The value to check.
* @template T - The list to check.
*
* @returns A boolean indicating whether `T` ends with `X`.
*
* @example
* For example, we can use `EndsWith` to check if a list ends with a value:
*
* ```ts
* import { $, List } from "hkt-toolbelt";
*
* type Result = $<$<List.EndsWith, [3, 4, 5]>, [1, 2, 3, 4, 5]>; // true
* ```
*/
export interface EndsWith extends Kind.Kind {
f(x: Type._$cast<this[Kind._], unknown[]>): EndsWith_T<typeof x>
}

/**
* Given a list and a value, return a boolean indicating whether the list
* ends with the value.
*
* @param {unknown[]} x - The value to check.
* @param {unknown[]} values - The list to check.
*
* @example
* ```ts
* import { List } from "hkt-toolbelt";
*
* const result = List.endsWith([3, 4, 5])([1, 2, 3, 4, 5])
* // ^? true
* ```
*/
export const endsWith = (x: unknown[]) => (values: unknown[]) => {
if (x.length === 0) return true

for (let i = 0; i < x.length; i++) {
if (x[i] !== values[values.length - x.length + i]) return false
}

return true
}
44 changes: 44 additions & 0 deletions src/list/find-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { $, Conditional, Function, List, String, Test } from '..'

type FindIndex_Spec = [
/**
* Can find the index of a number present in a tuple.
*/
Test.Expect<$<$<List.FindIndex, $<Conditional.Equals, 3>>, [1, 2, 3]>, 2>,

/**
* Can find the index of a string present in a tuple.
*/
Test.Expect<$<$<List.FindIndex, String.IsString>, [42, 'bar']>, 1>,

/**
* Can find the index of an element in a tuple.
*/
Test.Expect<
$<$<List.FindIndex, $<Conditional.Equals, 'foo'>>, ['foo', 'bar']>,
0
>,

/**
* Returns -1 if no element satisfies the predicate.
*/
Test.Expect<$<$<List.FindIndex, $<Conditional.Equals, 42>>, [1, 2, 3]>, -1>
]

it('should return the index of the first element in the list that satisfies the predicate', () => {
expect(List.findIndex(Conditional.equals('bar'))(['foo', 'bar', 'bar'])).toBe(
1
)
})

it('should return -1 if no element satisfies the predicate', () => {
expect(List.findIndex(String.isString)([42])).toBe(-1)
})

it('can find the index of a number present in a tuple', () => {
expect(List.findIndex(Function.constant(true))([1, 2, 3])).toBe(0)
})

it('can find the index of a string present in a tuple', () => {
expect(List.findIndex(String.isString)(['foo', 'bar'])).toBe(0)
})
67 changes: 67 additions & 0 deletions src/list/find-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { $, Kind, Type, DigitList, Function } from '..'

/**
* `_$findIndex` is a type-level function that takes in a predicate `F` and a
* list `T`, and returns the index of the first element in `T` that satisfies
* the predicate. Returns `-1` if no element satisfies the predicate.
*
* @template {Kind.Kind<(x: never) => boolean>} F - The predicate to check.
* @template {unknown[]} T - The list to check.
*
* @example
* ```ts
* type T0 = List._$findIndex<$<Conditional.Equals, 3>>, [1, 2, 3]> // 2
* ```
*/
export type _$findIndex<
F extends Kind.Kind<(x: never) => boolean>,
T extends unknown[],
I extends DigitList.DigitList = ['0']
> = T extends [infer Head, ...infer Tail]
? $<F, Type._$cast<Head, Kind._$inputOf<F>>> extends true
? DigitList._$toNumber<I>
: _$findIndex<F, Tail, DigitList._$increment<I>>
: -1

interface FindIndex_T<F extends Kind.Kind<(x: never) => boolean>>
extends Kind.Kind {
f(x: Type._$cast<this[Kind._], unknown[]>): _$findIndex<F, typeof x>
}

/**
* `FindIndex` is a type-level function that takes in a predicate `F` and a
* list `T`, and returns the index of the first element in `T` that satisfies
* the predicate. Returns `-1` if no element satisfies the predicate.
*
* @template {Kind.Kind<(x: never) => boolean>} F - The predicate to check.
* @template {unknown[]} T - The list to check.
*
* @example
* ```ts
* type T0 = $<$<List.FindIndex, $<Conditional.Equals, 3>>, [1, 2, 3]> // 2
* ```
*/
export interface FindIndex extends Kind.Kind {
f(
x: Type._$cast<this[Kind._], Kind.Kind<(x: never) => boolean>>
): FindIndex_T<typeof x>
}

/**
* Given a predicate and a list, return the index of the first element in the
* list that satisfies the predicate. Returns `-1` if no element satisfies the
* predicate.
*
* @param {Kind.Kind<(x: never) => boolean>} f - The predicate to check.
* @param {unknown[]} values - The list to check.
*
* @example
* ```ts
* import { List, String } from "hkt-toolbelt";
*
* const result = List.findIndex(String.isString)(['foo', 'bar'])
* // ^? 1
* ```
*/
export const findIndex = ((f: Function.Function) => (values: unknown[]) =>
values.findIndex(f as never)) as Kind._$reify<FindIndex>
10 changes: 9 additions & 1 deletion src/list/find.spec.ts → src/list/find.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $, Conditional, Function, List, Test } from '..'
import { $, Conditional, Function, List, Test, Type } from '..'

type Find_Spec = [
/**
Expand All @@ -25,3 +25,11 @@ type Find_Spec = [
// @ts-expect-error
List.Find<Function.Identity>
]

it('should return the first element in the list that satisfies the predicate', () => {
expect(List.find(Function.constant(true))([1, 2, 3])).toBe(1)
})

it('should return never if no element in the list satisfies the predicate', () => {
expect(List.find(Function.constant(false))([1, 2, 3])).toBe(Type.never)
})
Loading
Loading