Skip to content

Commit

Permalink
Merge pull request #94 from poteat/poteat/additional-reifications
Browse files Browse the repository at this point in the history
Poteat/additional reifications
  • Loading branch information
poteat authored Oct 7, 2024
2 parents a4b2495 + 33b7bc8 commit 99ef696
Show file tree
Hide file tree
Showing 39 changed files with 1,466 additions and 22 deletions.
18 changes: 17 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
# Changelog

## [0.24.8]

- Add `List.Count` to create a frequency map from a list.
- Add `List.Same` to check if all elements in a list are equal.
- Add `Object.AtPathInObject` to get the value at a path in an object.
- Reify `Object.AtPath` to a value-level function.
- Add `Object.Defaults` to initialize an object with default values.
- Add `Object.Omit` to remove keys from an object.
- Add `Object.Pick` to pick keys from an object.
- Reify `Object.Values` to a value-level function.
- Reify `List.Slice` and `List.Splice` to value-level functions.
- Add arg-swapped `List.SliceList` and `List.SpliceList` utilities.
- Add `List.Entries` to get the entries of a list as a list of 2-tuples.
- Add `String.Entries` to get the entries of a string as a list of 2-tuples.
- Add `Kind.Collapse` to convert an n-arity kind to a 1-arity kind.

## [0.24.7]

- Add `NaturalNumber.Digits` to get the digits of a natural number.
- Add `List.Of` to create a list containing a single value.
- Add `List.SlidingWindow` to slide a window of a certain length over a list.
- Reify various natural number utilities.
- Reify various string and list utilities.
- Fix statefulness bug in reified `List.chunk`.
- Fix statefulness bug in reified `List.collate`.

## [0.24.6]
- Add `String.FromCharCode` to convert a character code to a string.
Expand Down
12 changes: 12 additions & 0 deletions src/kind/collapse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { $, Combinator, Kind, Test } from '..'

type Collapse_Spec = [
/**
* Can collapse a kind.
*/
Test.Expect<$<$<Kind.Collapse, $<Combinator.Collate, 2>>, 1>, [1, 1]>
]

it('should collapse a kind', () => {
expect(Kind.collapse(Combinator.collate(2))(1)).toEqual([1, 1])
})
71 changes: 71 additions & 0 deletions src/kind/collapse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { $, Function, Kind, Type } from '..'

/**
* `_$collapse` is a type-level function that takes in a kind `K` and a value
* `X`, and repeatedly applies the current function to the value until it no
* longer returns a kind.
*
* @template {Kind.Kind} K - The kind to collapse.
* @template {unknown} X - The value to collapse with.
*
* @example
* ```ts
* import { $, Kind, Combinator } from "hkt-toolbelt";
*
* type T0 = _$collapse<$<Combinator.Collate, 2>, 1> // [2, 2]
* ```
*/
export type _$collapse<K extends Kind.Kind, X extends Kind._$inputOf<K>> =
$<K, X> extends infer NewValue
? NewValue extends Kind.Kind
? _$collapse<NewValue, Type._$cast<X, Kind._$inputOf<NewValue>>>
: NewValue
: never

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

/**
* `Collapse` is a type-level function that takes in a kind `K` and a value
* `X`, and repeatedly applies the current function to the value until it no
* longer returns a kind.
*
* @template {Kind.Kind} K - The kind to collapse.
* @template {unknown} X - The value to collapse with.
*
* @example
* ```ts
* import { $, Kind, Combinator } from "hkt-toolbelt";
*
* type T0 = $<$<Kind.Collapse, $<Combinator.Collate, 2>>, 1> // [2, 2]
* ```
*/
export interface Collapse extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Kind.Kind>): Collapse_T<typeof x>
}

/**
* Given a kind `K` and a value `X`, repeatedly apply the current function to
* the value until it no longer returns a kind.
*
* @param {Kind.Kind} k - The kind to collapse.
* @param {unknown} x - The value to collapse with.
*
* @example
* ```ts
* import { Kind, Combinator } from "hkt-toolbelt";
*
* const result = Kind.collapse(Combinator.collate(2))(1)
* // ^? [1, 1]
* ```
*/
export const collapse = ((k: Function.Function) => (x: unknown) => {
let f = k

while (typeof f === 'function') {
f = f(x as never) as Function.Function
}

return f
}) as unknown as Kind._$reify<Collapse>
8 changes: 0 additions & 8 deletions src/kind/curry.spec.ts

This file was deleted.

15 changes: 15 additions & 0 deletions src/kind/curry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Test, $N, Kind, Function, List } from '..'

type Curry_Spec = [
Test.Expect<
$N<Kind.Curry, [2, Function.Identity, 'foo', 'bar']>,
['foo', 'bar']
>
]

it('should curry a function', () => {
const myFcn = Kind.curry(2)(List.same)

expect(myFcn('foo')('bar')).toBe(false)
expect(myFcn('foo')('foo')).toBe(true)
})
35 changes: 34 additions & 1 deletion src/kind/curry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $, Kind, Number, NaturalNumber, Conditional, Type } from '..'
import { $, Kind, Number, NaturalNumber, Conditional, Type, Function } from '..'

/**
* `_$curry` takes in a positive natural number `N` and a type-level function
Expand Down Expand Up @@ -81,3 +81,36 @@ interface Curry_T<N extends number> extends Kind.Kind {
export interface Curry extends Kind.Kind {
f(x: Type._$cast<this[Kind._], number>): Curry_T<typeof x>
}

/**
* Given a number of arguments N and a function F that takes in a N-tuple,
* return a new function of arity N that wraps F.
*
* This converts a function that takes in a tuple to a function that takes in
* a certain number of arguments, in a curried form.
*
* @param {number} n - The number of arguments to curry.
* @param {Kind.Kind} f - The function to curry.
*
* @example
* ```ts
* import { Kind, Function } from "hkt-toolbelt";
*
* const myFcn = Kind.curry(2)(List.same)
*
* const result = myFcn(1)(2) // false
* ```
*/
export const curry = ((n: number) => (f: Function.Function) => {
const collector =
(values: unknown[] = []) =>
(value: unknown) => {
const newValues = [...values, value]
if (newValues.length === n) {
return f(newValues as never)
} else {
return collector(newValues)
}
}
return collector()
}) as Kind._$reify<Curry>
1 change: 1 addition & 0 deletions src/kind/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './apply'
export * from './apply-n'
export * from './arity'
export * from './collapse'
export * from './composable'
export * from './composable-pair'
export * from './compose'
Expand Down
13 changes: 11 additions & 2 deletions src/kind/reify.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $, Function, Kind, List, String, Test } from '..'
import { $, Function, Kind, List, String, Test, Object } from '..'

declare const map: $<Kind.Reify, List.Map>
declare const append: $<Kind.Reify, String.Append>
Expand All @@ -9,6 +9,10 @@ const f = pipe([map(append('!')), join(' ')])

const x = f(['hello', 'world']) // 'hello! world!'

const y = List.map(Object.atPathInObject({ foo: { bar: 'baz' } } as const))([
['foo', 'bar']
])

type Reify_Spec = [
/**
* The return type of a reified type is unknown.
Expand All @@ -18,5 +22,10 @@ type Reify_Spec = [
/**
* Can reify a type-level function with correct type inference.
*/
Test.Expect<typeof x, 'hello! world!'>
Test.Expect<typeof x, 'hello! world!'>,

/**
* Can perform deep object literal inference.
*/
Test.Expect<typeof y, ['baz']>
]
65 changes: 65 additions & 0 deletions src/list/count.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { $, Test, List } from '..'

type Count_Spec = [
/**
* Can count the number of occurrences of each key in a list.
*/
Test.Expect<
$<List.Count, ['foo', 'foo', 'bar']>,
{
foo: 2
bar: 1
}
>,

/**
* Can count the number of occurrences of each key in a list.
*/
Test.Expect<
$<List.Count, ['foo', 'foo', 'bar', 'baz']>,
{
foo: 2
bar: 1
baz: 1
}
>,

/**
* Counting an empty list results in an empty map.
*/
Test.Expect<$<List.Count, []>, {}>,

/**
* Counting a list with a single element results in a map with one entry.
*/
Test.Expect<$<List.Count, ['foo']>, { foo: 1 }>,

/**
* Counting a list of non-property keys results in a type error.
*/
// @ts-expect-error
Test.Expect<$<List.Count, ['foo', { foo: 'foo' }]>>
]

it('should count the number of occurrences of each key in a list', () => {
expect(List.count(['foo', 'foo', 'bar'])).toEqual({
foo: 2,
bar: 1
})
})

it('should count the number of occurrences of each key in a list', () => {
expect(List.count(['foo', 'foo', 'bar', 'baz'])).toEqual({
foo: 2,
bar: 1,
baz: 1
})
})

it('should count an empty list as an empty map', () => {
expect(List.count([])).toEqual({})
})

it('should count a list with a single element as a map with one entry', () => {
expect(List.count(['foo'])).toEqual({ foo: 1 })
})
89 changes: 89 additions & 0 deletions src/list/count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { NaturalNumber, Object, List, Kind, Type } from '..'

type _$count2<
T extends unknown[],
O extends Record<string | number | symbol, number> = {}
> = T extends [infer Head, ...infer Tail]
? _$count2<
Tail,
Head extends PropertyKey
? Object._$assign<
Head,
Head extends keyof O ? NaturalNumber._$increment<O[Head]> : 1,
O
>
: never
>
: O

/**
* `_$count` is a type-level function that takes in a list of property keys and
* returns a map from each key to the number of times it appears in the list.
*
* Similar to `List._$countBy`, but uses the identity function as the counting
* function.
*
* @template {PropertyKey[]} K - The list of property keys to count.
*
* @example
* ```ts
* type T0 = _$count<['foo', 'foo', 'bar']> // { foo: 2, bar: 1 }
* ```
*/
export type _$count<K extends PropertyKey[]> =
List._$isVariadic<K> extends true
? Record<K[number], number>
: Type._$display<_$count2<K>>

/**
* `Count` is a type-level function that takes in a list of property keys and
* returns a map from each key to the number of times it appears in the list.
*
* Similar to `List.CountBy`, but uses the identity function as the counting
* function.
*
* @template {PropertyKey[]} K - The list of property keys to count.
*
* @example
* ```ts
* type T0 = $<$<List.Count, ['foo', 'foo', 'bar']> // { foo: 2, bar: 1 }
* ```
*/
export interface Count extends Kind.Kind {
f(x: Type._$cast<this[Kind._], PropertyKey[]>): _$count<typeof x>
}

/**
* Given a list of property keys, return a map from each key to the number of
* times it appears in the list.
*
* Similar to `List.CountBy`, but uses the identity function as the counting
* function.
*
* @param {PropertyKey[]} k - The list of property keys to count.
*
* @example
* ```ts
* import { List } from "hkt-toolbelt";
*
* const result = List.count(['foo', 'foo', 'bar'])
* // ^? { foo: 2, bar: 1 }
* ```
*/
export const count = ((k: PropertyKey[]) => {
const result = {} as Record<PropertyKey, number>

for (const key of k) {
if (
typeof key !== 'string' &&
typeof key !== 'number' &&
typeof key !== 'symbol'
) {
return Type.never
}

result[key] = key in result ? result[key] + 1 : 1
}

return result
}) as Kind._$reify<Count>
Loading

0 comments on commit 99ef696

Please sign in to comment.