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 methods #92

Merged
merged 10 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.6]
- Add `String.FromCharCode` to convert a character code to a string.
- Add `String.IsLetter` to check if a string is a letter.
- Add `String.ToCharCode` to convert a string to a character code.
- Reify `String.Split` to a value-level function.
- Reify `String.ToLower` to a value-level function.
- Reify `NaturalNumber.SubtractBy` to a value-level function.
- Reify `NaturalNumber.Subtract` to a value-level function.
- Reify `List.Filter` to a value-level function.

## [0.24.5]
- Add `NaturalNumber.Square` to compute the square of a natural number.
- Add `Object.AtKey` to get the value at a key in an object. (swapped argument order of `Object.At`)
Expand Down
18 changes: 17 additions & 1 deletion src/list/filter.spec.ts → src/list/filter.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $, Conditional, Function, Kind, List, Test } from '..'
import { $, Conditional, Function, Kind, List, NaturalNumber, Test } from '..'

type Filter_Spec = [
/**
Expand Down Expand Up @@ -58,3 +58,19 @@ type Filter_Spec = [
[1, 2, 3]
>
]

it('should not filter out when predicate always returns true', () => {
expect(List.filter(Function.constant(true))([1, 2, 3, 4, 5])).toEqual([
1, 2, 3, 4, 5
])
})

it('should filter out when predicate always returns false', () => {
expect(List.filter(Function.constant(false))([1, 2, 3, 4, 5])).toEqual([])
})

it('can filter out numbers that are not greater than 3', () => {
expect(List.filter(NaturalNumber.isGreaterThan(3))([1, 2, 3, 4, 5])).toEqual([
4, 5
])
})
20 changes: 19 additions & 1 deletion src/list/filter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $, Type, Kind } from '..'
import { $, Type, Kind, Function } from '..'

/**
* `_$filter` is a type-level function that takes in two inputs:
Expand Down Expand Up @@ -82,3 +82,21 @@ export interface Filter extends Kind.Kind {
x: Type._$cast<this[Kind._], Kind.Kind<(x: never) => boolean>>
): Filter_T<typeof x>
}

/**
* Given a predicate and a list, filter the list to only include elements that
* satisfy the predicate.
*
* @param {Kind.Kind<(x: never) => boolean>} f - The predicate to filter the list by.
* @param {unknown[]} values - The list to filter.
*
* @example
* ```ts
* import { List, String } from "hkt-toolbelt";
*
* const result = List.filter(NaturalNumber.isGreaterThan(3))([1, 2, 3, 4, 5])
* // ^? [4, 5]
* ```
*/
export const filter = ((f: Function.Function) => (values: unknown[]) =>
values.filter((value) => f(value as never))) as Kind._$reify<Filter>
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ type SubtractBy_Spec = [
*/
Test.Expect<$<$<NaturalNumber.SubtractBy, 456>, 123>, 0>
]

it('should subtract two numbers', () => {
expect(NaturalNumber.subtractBy(123)(456)).toBe(333)
})

it('caps result at zero', () => {
expect(NaturalNumber.subtractBy(456)(123)).toBe(0)
})
18 changes: 18 additions & 0 deletions src/natural-number/subtract-by.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,21 @@ export interface SubtractBy extends Kind.Kind {
x: Type._$cast<this[Kind._], Number.Number>
): Number._$isNatural<typeof x> extends true ? SubtractBy_T<typeof x> : never
}

/**
* Given two natural numbers, subtract the first number from the second. The
* result is capped at zero.
*
* @param {number} a - The first number.
* @param {number} b - The second number.
*
* @example
* ```ts
* import { NaturalNumber } from "hkt-toolbelt";
*
* const result = NaturalNumber.subtractBy(2)(3)
* // ^? 1
* ```
*/
export const subtractBy = ((a: number) => (b: number) =>
a > b ? 0 : b - a) as Kind._$reify<SubtractBy>
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ type Subtract_Spec = [
*/
Test.Expect<$<$<NaturalNumber.Subtract, 123>, 456>, 0>
]

it('should subtract two numbers', () => {
expect(NaturalNumber.subtract(456)(123)).toBe(333)
})

it('result is capped at zero', () => {
expect(NaturalNumber.subtract(123)(456)).toBe(0)
})
17 changes: 17 additions & 0 deletions src/natural-number/subtract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,20 @@ export interface Subtract extends Kind.Kind {
x: Type._$cast<this[Kind._], Number.Number>
): Number._$isNatural<typeof x> extends true ? Subtract_T<typeof x> : never
}

/**
* Given two natural numbers, subtract the second number from the first.
*
* @param {number} a - The first number.
* @param {number} b - The second number.
*
* @example
* ```ts
* import { NaturalNumber } from "hkt-toolbelt";
*
* const result = NaturalNumber.subtract(3)(2)
* // ^? 1
* ```
*/
export const subtract = ((a: number) => (b: number) =>
b > a ? 0 : a - b) as Kind._$reify<Subtract>
33 changes: 33 additions & 0 deletions src/string/from-char-code.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { $, String, Test } from '..'

type FromCharCode_Spec = [
/**
* Can get the character of a character code.
*/
Test.Expect<$<String.FromCharCode, 102>, 'f'>,

/**
* Can get the character of a space.
*/
Test.Expect<$<String.FromCharCode, 32>, ' '>,

/**
* An empty string results in never.
*/
Test.Expect<$<String.FromCharCode, 0>, never>,

/**
* Non-number input results in a compiler error.
*/
// @ts-expect-error
$<String.FromCharCode, string>,

/**
* Non-ASCII characters result in never
*/
Test.Expect<$<String.FromCharCode, 12345>, never>
]

it('should return the character of a character code', () => {
expect(String.fromCharCode(102)).toBe('f')
})
154 changes: 154 additions & 0 deletions src/string/from-char-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Kind, Type } from '..'

type _$charCodeMapInverse = {
97: 'a'
98: 'b'
99: 'c'
100: 'd'
101: 'e'
102: 'f'
103: 'g'
104: 'h'
105: 'i'
106: 'j'
107: 'k'
108: 'l'
109: 'm'
110: 'n'
111: 'o'
112: 'p'
113: 'q'
114: 'r'
115: 's'
116: 't'
117: 'u'
118: 'v'
119: 'w'
120: 'x'
121: 'y'
122: 'z'
65: 'A'
66: 'B'
67: 'C'
68: 'D'
69: 'E'
70: 'F'
71: 'G'
72: 'H'
73: 'I'
74: 'J'
75: 'K'
76: 'L'
77: 'M'
78: 'N'
79: 'O'
80: 'P'
81: 'Q'
82: 'R'
83: 'S'
84: 'T'
85: 'U'
86: 'V'
87: 'W'
88: 'X'
89: 'Y'
90: 'Z'
48: '0'
49: '1'
50: '2'
51: '3'
52: '4'
53: '5'
54: '6'
55: '7'
56: '8'
57: '9'
32: ' '
33: '!'
34: '"'
35: '#'
36: '$'
37: '%'
38: '&'
39: "'"
40: '('
41: ')'
42: '*'
43: '+'
44: ','
45: '-'
46: '.'
47: '/'
58: ':'
59: ';'
60: '<'
61: '='
62: '>'
63: '?'
64: '@'
91: '['
92: '\\'
93: ']'
94: '^'
95: '_'
96: '`'
123: '{'
124: '|'
125: '}'
126: '~'
9: '\t'
10: '\n'
13: '\r'
12: '\f'
11: '\v'
}

/**
* `_$fromCharCode` is a type-level function that takes in a number `N` and
* returns the character corresponding to that character code. This is only valid
* for ASCII character codes.
*
* @template {number} N - The character code to get the character for.
*
* @example
* ```
* type T0 = _$fromCharCode<102> // 'f'
* type T1 = _$fromCharCode<98> // 'b'
* ```
*/
export type _$fromCharCode<N extends number> =
N extends keyof _$charCodeMapInverse ? _$charCodeMapInverse[N] : never

/**
* `FromCharCode` is a type-level function that takes in a number `N` and
* returns the character corresponding to that character code. This is only valid
* for ASCII character codes.
*
* @template {number} N - The character code to get the character for.
*
* @example
* ```ts
* import { $, String } from "hkt-toolbelt";
*
* type Result = $<String.FromCharCode, 102> // 'f'
* ```
*/
export interface FromCharCode extends Kind.Kind {
f(x: Type._$cast<this[Kind._], number>): _$fromCharCode<typeof x>
}

/**
* Given a character code, return the corresponding character.
*
* @param {number} x - The character code to get the character for.
*
* @example
* ```ts
* import { String } from "hkt-toolbelt";
*
* const result = String.fromCharCode(102)
* // ^? 'f'
* ```
*/
export const fromCharCode = ((x: number) =>
String.fromCharCode(x)) as Kind._$reify<FromCharCode>
3 changes: 3 additions & 0 deletions src/string/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ export * from './append'
export * from './capitalize'
export * from './ends-with'
export * from './first'
export * from './from-char-code'
export * from './from-list'
export * from './includes'
export * from './init'
export * from './is-letter'
export * from './is-string'
export * from './is-template'
export * from './join'
Expand All @@ -17,6 +19,7 @@ export * from './slice'
export * from './split'
export * from './starts-with'
export * from './tail'
export * from './to-char-code'
export * from './to-list'
export * from './to-lower'
export * from './to-upper'
31 changes: 31 additions & 0 deletions src/string/is-letter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { $, String, Test } from '..'

type IsLetter_Spec = [
/**
* Can check if a string is a letter.
*/
Test.Expect<$<String.IsLetter, 'f'>, true>,

/**
* Can check if a string is not a letter.
*/
Test.Expect<$<String.IsLetter, '9'>, false>,

/**
* An empty string is not a letter.
*/
Test.Expect<$<String.IsLetter, ''>, false>,

/**
* A template literal string is not a letter.
*/
Test.Expect<$<String.IsLetter, `f${string}o`>, false>
]

it('should check if a string is a letter', () => {
expect(String.isLetter('f')).toBe(true)
})

it('returns false for an empty string', () => {
expect(String.isLetter('')).toBe(false)
})
Loading
Loading