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 Dec 15, 2024
2 parents 262d66e + a641ed1 commit c7d3e31
Show file tree
Hide file tree
Showing 68 changed files with 2,695 additions and 31 deletions.
25 changes: 25 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## [0.25.0]

- Reify `NaturalNumber.Modulo` and `NaturalNumber.ModuloBy` to value-level functions.
- Reify `Number.Negate` to a value-level function.
- Reify `List.Zip`.
- Add `Integer.Negate`.
- Add `List.Sort` and `List.SortBy`.
- Reify `String.StartsWith` and `String.EndsWith` to value-level functions.
- Add `Object.PickByValue`.
- Reify `Type.Display` to an identity function.
- Improve pipe performance.
- Reify `List.Intersect`.
- Add `List.Subtract` and `List.SubtractBy`.
- Add `Kind.ApplyKind`.
- Reify `List.Unshift`, `List.FlatMap`, and `List.Times`.
- Add `String.Compare` and `String.CompareChar`.
- Add `List.Compare` for comparing lists.
- Reify `Number.Compare` to a value-level function.
- Add `Type.TypeOf` as a kind for emulating the `typeof` operator.
- Add `List.UniqueBy` for getting unique values from a list and mapping.
- Reify `Number.IsInteger` to a value-level function.
- Add `String.Repeat` for repeating a string.
- Add `List.CartesianProduct` for generating all possible combinations of elements.
- Fix edge condition in `String.Join` involving empty values.

## [0.24.11]

- Reify `String.ToList` to a value-level function.
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} **/
module.exports = {
testEnvironment: 'node',
roots: ["src"],
transform: {
'^.+.tsx?$': ['ts-jest', {}]
},
Expand Down
1 change: 1 addition & 0 deletions src/integer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './is-odd'
export * from './modulo'
export * from './modulo-by'
export * from './multiply'
export * from './negate'
export * from './remainder'
export * from './remainder-by'
export * from './subtract'
Expand Down
35 changes: 35 additions & 0 deletions src/integer/negate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { $, Test, Integer } from '..'

type Negate_Spec = [
/**
* Can get the sign of a positive number.
*/
Test.Expect<$<Integer.Negate, 42>, -42>,

/**
* Can get the sign of a negative number.
*/
Test.Expect<$<Integer.Negate, -42>, 42>,

/**
* Can get the sign of zero.
*/
Test.Expect<$<Integer.Negate, 0>, 0>,

/**
* Evaluating the 'number' type properly.
*/
Test.Expect<$<Integer.Negate, number>, number>
]

it('should return the negation of a number', () => {
expect(Integer.negate(42)).toBe(-42)
})

it('should return the negation of a number', () => {
expect(Integer.negate(-42)).toBe(42)
})

it('should return the negation of a number', () => {
expect(Integer.negate(0)).toBe(0)
})
52 changes: 52 additions & 0 deletions src/integer/negate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Kind, Type, Number as Number_ } from '..'

/**
* `_$negate` is a type-level function that takes in an integer `T`, and
* returns the integer negation of `T`.
*
* @template {number} T - An integer.
*
* @example
* ```ts
* import { Integer } from "hkt-toolbelt";
*
* type Result = Integer._$negate<42>; // -42
* ```
*/
export type _$negate<
T extends Number_.Number,
NEG extends Number_.Number = Number_._$negate<T>
> = NEG

/**
* `Negate` is a type-level function that takes in an integer `T`, and
* returns the integer negation of `T`.
*
* @template {number} T - An integer.
*
* @example
* ```ts
* import { $, Integer } from "hkt-toolbelt";
*
* type Result = $<Integer.Negate, 42>; // -42
* ```
*/
export interface Negate extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Number_.Number>): _$negate<typeof x>
}

/**
* Given an integer, return its negation.
*
* @param {number} x - The integer to negate.
*
* @example
* ```ts
* import { Integer } from "hkt-toolbelt";
*
* const result = Integer.negate(42)
* // ^? -42
* ```
*/
export const negate = ((x: Number_.Number) =>
Number(x) === 0 ? 0 : -Number(x)) as Kind._$reify<Negate>
12 changes: 12 additions & 0 deletions src/kind/apply-kind.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { $, Kind, Function, Test } from '..'

type ApplyKind_Spec = [
/**
* Can apply a kind to a value.
*/
Test.Expect<$<$<Kind.ApplyKind, Function.Identity>, [1, 2, 3]>, [1, 2, 3]>
]

it('should apply a kind to a value', () => {
expect(Kind.applyKind(Function.identity)([1, 2, 3])).toEqual([1, 2, 3])
})
60 changes: 60 additions & 0 deletions src/kind/apply-kind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { $, Kind, Type, Function } from '..'

/**
* `_$applyKind` is a type-level function that takes in a kind `K` and a value `X`,
* and applies the kind to the value.
*
* @template K - The kind to apply
* @template X - The value to apply K to
*
* @example
* ```ts
* import { Kind, Function } from "hkt-toolbelt";
*
* type Result = Kind._$applyKind<Function.Identity, [1, 2, 3]>; // [1, 2, 3]
* ```
*/
export type _$applyKind<K extends Kind.Kind, X> = $<
K,
Type._$cast<X, Kind._$inputOf<K>>
>

interface ApplyKind_T<K extends Kind.Kind> extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Kind._$inputOf<K>>): _$applyKind<K, typeof x>
}

/**
* `ApplyKind` is a type-level function that takes in a kind `K` and a value `X`,
* and applies the kind to the value.
*
* @template K - The kind to apply
* @template X - The value to apply K to
*
* @example
* ```ts
* import { $, Kind, Function } from "hkt-toolbelt";
*
* type Result = $<$<Kind.ApplyKind, Function.Identity>, [1, 2, 3]>; // [1, 2, 3]
* // Equivalent to $<Function.Identity, [1, 2, 3]>
* ```
*/
export interface ApplyKind extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Kind.Kind>): ApplyKind_T<typeof x>
}

/**
* Given a kind and a value, apply the kind to the value.
*
* @param {Kind.Kind} k - The kind to apply.
* @param {unknown} x - The value to apply the kind to.
*
* @example
* ```ts
* import { Kind, Function } from "hkt-toolbelt";
*
* const result = Kind.applyKind(Function.identity)([1, 2, 3])
* // ^? [1, 2, 3]
* ```
*/
export const applyKind = ((k: Function.Function) => (x: unknown) =>
k(x as never)) as Kind._$reify<ApplyKind>
4 changes: 2 additions & 2 deletions src/kind/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { $, Kind, Type, Function } from '..'
* It takes a value X and kind K, casts X to the input type of K, and applies
* K to the casted X using the `$` operator.
*
* @template K - The kind to apply
* @template X - The value to apply K to
* @template K - The kind to apply
*
* @returns The result of applying K to the casted X
*/
Expand Down Expand Up @@ -73,7 +73,7 @@ export interface Apply extends Kind.Kind {
}

/**
* Given a kind and a value, apply the kind to the value.
* Given a value and a kind, apply the kind to the value.
*
* @param {unknown} x - The value to apply the kind to.
* @param {Kind.Kind} f - The kind to apply.
Expand Down
3 changes: 2 additions & 1 deletion src/kind/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './apply'
export * from './apply-kind'
export * from './apply-n'
export * from './apply'
export * from './arity'
export * from './collapse'
export * from './composable'
Expand Down
2 changes: 1 addition & 1 deletion src/kind/input-of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Type, Kind } from '..'
* type InputType = _$inputOf<ExampleKind>; // This will be inferred as number
*/
export type _$inputOf<F extends Kind.Kind> = F extends {
f: (x: infer X) => unknown
f: (x: infer X) => any
}
? X
: unknown
Expand Down
99 changes: 99 additions & 0 deletions src/list/cartesian-product.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { $, List, Test } from '..'

type CartesianProduct_Spec = [
// Single element, single value
Test.Expect<$<List.CartesianProduct, ['foo']>, [['foo']]>,

// Two elements, both single values
Test.Expect<$<List.CartesianProduct, ['foo', 'bar']>, [['foo', 'bar']]>,

// Mix single and tuple
Test.Expect<
$<List.CartesianProduct, ['foo', ['bar', 'qux']]>,
[['foo', 'bar'], ['foo', 'qux']]
>,

// Multiple tuples
Test.Expect<
$<List.CartesianProduct, [['a', 'b'], ['c', 'd']]>,
[['a', 'c'], ['a', 'd'], ['b', 'c'], ['b', 'd']]
>,

// More complex example
Test.Expect<
$<List.CartesianProduct, [['x'], ['y', 'z'], [1, 2]]>,
[['x', 'y', 1], ['x', 'y', 2], ['x', 'z', 1], ['x', 'z', 2]]
>
]

describe('List.cartesianProduct (runtime)', () => {
it('returns [[]] for an empty input', () => {
expect(List.cartesianProduct([])).toEqual([[]])
})

it('handles a single element that is not an array', () => {
expect(List.cartesianProduct(['foo'])).toEqual([['foo']])
})

it('handles multiple single-value elements', () => {
// ["foo", "bar"] -> [["foo","bar"]]
expect(List.cartesianProduct(['foo', 'bar'])).toEqual([['foo', 'bar']])
})

it('handles a mix of single values and arrays', () => {
// ["foo", ["bar","qux"]]
// Expect: [["foo","bar"],["foo","qux"]]
const result = List.cartesianProduct(['foo', ['bar', 'qux']])
expect(result).toEqual([
['foo', 'bar'],
['foo', 'qux']
])
})

it('handles multiple arrays', () => {
// [["a","b"], ["c","d"]]
// Expect: [["a","c"],["a","d"],["b","c"],["b","d"]]
const result = List.cartesianProduct([
['a', 'b'],
['c', 'd']
])
expect(result).toEqual([
['a', 'c'],
['a', 'd'],
['b', 'c'],
['b', 'd']
])
})

it('handles more complex examples', () => {
// [["x"], ["y","z"], [1,2]]
// Expect: [
// ["x","y",1],
// ["x","y",2],
// ["x","z",1],
// ["x","z",2]
// ]
const result = List.cartesianProduct([['x'], ['y', 'z'], [1, 2]])
expect(result).toEqual([
['x', 'y', 1],
['x', 'y', 2],
['x', 'z', 1],
['x', 'z', 2]
])
})

it('maintains order correctly', () => {
// Order verification:
// For [["a","b"], ["c","d"]], we specifically want [["a","c"],["a","d"],["b","c"],["b","d"]]
const result = List.cartesianProduct([
['a', 'b'],
['c', 'd']
])
expect(result).toEqual([
['a', 'c'],
['a', 'd'],
['b', 'c'],
['b', 'd']
])
})
})
Loading

0 comments on commit c7d3e31

Please sign in to comment.