Skip to content

Commit

Permalink
Merge branch 'main' of github.com:poteat/hkt-toolbelt
Browse files Browse the repository at this point in the history
  • Loading branch information
poteat committed Oct 13, 2024
2 parents 665adc2 + c0a1e64 commit 9967807
Show file tree
Hide file tree
Showing 44 changed files with 1,683 additions and 12 deletions.
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

0 comments on commit 9967807

Please sign in to comment.