Skip to content

Commit

Permalink
Merge pull request #91 from poteat/poteat/new-reified-implementations
Browse files Browse the repository at this point in the history
Add reified implementations for various methods
  • Loading branch information
poteat authored Oct 4, 2024
2 parents 5683974 + 43408bc commit 420f6ea
Show file tree
Hide file tree
Showing 28 changed files with 423 additions and 52 deletions.
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@
"prefer-const": "error", // ts provides better types with const
"prefer-rest-params": "error", // ts provides better types with rest args over arguments
"prefer-spread": "error", // ts transpiles spread to apply, so no need for manual apply
"valid-typeof": "off" // ts(2367)
"valid-typeof": "off", // ts(2367)
"no-constant-condition": "off"
},
"settings": {
"import/parsers": {
Expand Down
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [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`)
- Modify `Conditional.equals` to perform deep equality.
- Add various value-level implementations.

## [0.24.4]
- Add various value-level implementations across `List`, `NaturalNumber`, `String`, and `Type`.
- Add `Type.Never` to represent the `never` type.
Expand Down
67 changes: 67 additions & 0 deletions src/_internal/deepEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
export function deepEqual(a: unknown, b: unknown): boolean {
if (Object.is(a, b)) return true

if (
a &&
b &&
typeof a === 'object' &&
typeof b === 'object' &&
Object.getPrototypeOf(a) === Object.getPrototypeOf(b)
) {
if (Array.isArray(a)) {
if (!Array.isArray(b)) return false
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) return false
}
return true
}

if (a instanceof Map) {
if (!(b instanceof Map)) return false
if (a.size !== b.size) return false
for (const [key, val] of a) {
if (!b.has(key)) return false
if (!deepEqual(val, b.get(key))) return false
}
return true
}

if (a instanceof Set) {
if (!(b instanceof Set)) return false
if (a.size !== b.size) return false
for (const val of a) {
if (!b.has(val)) return false
}
return true
}

if (a instanceof Date) {
if (!(b instanceof Date)) return false
return a.getTime() === b.getTime()
}

if (a instanceof RegExp) {
if (!(b instanceof RegExp)) return false
return a.toString() === b.toString()
}

const aKeys = Object.keys(a as Record<string, unknown>)
const bKeys = Object.keys(b as Record<string, unknown>)
if (aKeys.length !== bKeys.length) return false

for (const key of aKeys) {
if (
!deepEqual(
(a as Record<string, unknown>)[key],
(b as Record<string, unknown>)[key]
)
)
return false
}

return true
}

return false
}
8 changes: 8 additions & 0 deletions src/boolean/and-all.spec.ts → src/boolean/and-all.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ type AndAll_Spec = [
// @ts-expect-error
Test.Expect<$<$<Boolean.AndAll, true>, number>>
]

it('should return true if all elements are true', () => {
expect(Boolean.andAll([true, true, true])).toBe(true)
})

it('should return false if any element is false', () => {
expect(Boolean.andAll([true, true, false])).toBe(false)
})
16 changes: 16 additions & 0 deletions src/boolean/and-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,19 @@ type TrueList<
export interface AndAll extends Kind.Kind {
f(x: Type._$cast<this[Kind._], boolean[]>): _$andAll<typeof x>
}

/**

Check warning on line 53 in src/boolean/and-all.ts

View workflow job for this annotation

GitHub Actions / build-stress

Missing JSDoc @returns declaration
* Given a list of booleans, return whether all elements are true.
*
* @param {boolean[]} b - The list of booleans.

Check warning on line 56 in src/boolean/and-all.ts

View workflow job for this annotation

GitHub Actions / build-stress

Types are not permitted on @param
*
* @example
* ```ts
* import { Boolean } from "hkt-toolbelt";
*
* const result = Boolean.andAll([true, true, true])
* // ^? true
* ```
*/
export const andAll = ((b: boolean[]) =>
b.every((e) => e)) as Kind._$reify<AndAll>
8 changes: 8 additions & 0 deletions src/boolean/and.spec.ts → src/boolean/and.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ type And_Spec = [
// @ts-expect-error
Test.Expect<$<Boolean.And<true>, number>>
]

it('should return true if both inputs are true', () => {
expect(Boolean.and(true)(true)).toBe(true)
})

it('should return false if one input is false', () => {
expect(Boolean.and(true)(false)).toBe(false)
})
16 changes: 16 additions & 0 deletions src/boolean/and.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,19 @@ interface And_T<T extends boolean> extends Kind.Kind {
export interface And extends Kind.Kind {
f(x: Type._$cast<this[Kind._], boolean>): And_T<typeof x>
}

/**

Check warning on line 60 in src/boolean/and.ts

View workflow job for this annotation

GitHub Actions / build-stress

Missing JSDoc @returns declaration
* Given two booleans, return whether both are true.
*
* @param {boolean} a - The first boolean.

Check warning on line 63 in src/boolean/and.ts

View workflow job for this annotation

GitHub Actions / build-stress

Types are not permitted on @param
* @param {boolean} b - The second boolean.

Check warning on line 64 in src/boolean/and.ts

View workflow job for this annotation

GitHub Actions / build-stress

@param "b" does not match an existing function parameter

Check warning on line 64 in src/boolean/and.ts

View workflow job for this annotation

GitHub Actions / build-stress

Types are not permitted on @param
*
* @example
* ```ts
* import { Boolean } from "hkt-toolbelt";
*
* const result = Boolean.and(true)(false)
* // ^? false
* ```
*/
export const and = ((a: boolean) => (b: boolean) => a && b) as Kind._$reify<And>
49 changes: 2 additions & 47 deletions src/combinator/fix-sequence.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { $, Kind, Type, Function } from '..'
import { deepEqual } from '../_internal/deepEqual'

/**
* _$fixSequence is a type-level function that generates a fixed-point sequence
Expand Down Expand Up @@ -202,7 +203,7 @@ export interface FixSequence extends Kind.Kind {
* ```
*/
export const fixSequence = ((f: Function.Function) => (x: unknown) => {
let state = [x]
const state = [x]

let previousValue = undefined
let value = x
Expand All @@ -219,49 +220,3 @@ export const fixSequence = ((f: Function.Function) => (x: unknown) => {
previousValue = value
}
}) as Kind._$reify<FixSequence>

function deepEqual(a: any, b: any): boolean {
if (a === b) return true

if (a && b && typeof a === 'object' && typeof b === 'object') {
if (a.constructor !== b.constructor) return false

if (Array.isArray(a)) {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) return false
}
return true
}

if (a instanceof Map) {
if (a.size !== b.size) return false
for (const [key, val] of a) {
if (!b.has(key) || !deepEqual(val, b.get(key))) return false
}
return true
}

if (a instanceof Set) {
if (a.size !== b.size) return false
for (const val of a) {
if (!b.has(val)) return false
}
return true
}

if (a instanceof Date) return a.getTime() === b.getTime()
if (a instanceof RegExp) return a.toString() === b.toString()

const keys = Object.keys(a)
if (keys.length !== Object.keys(b).length) return false

for (const key of keys) {
if (!deepEqual(a[key], b[key])) return false
}

return true
}

return Number.isNaN(a) && Number.isNaN(b)
}
4 changes: 4 additions & 0 deletions src/conditional/equals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ it('should return true for equal values', () => {
it('should return false for unequal values', () => {
expect(Conditional.equals(1)(2)).toBe(false)
})

it('performs deep equality', () => {
expect(Conditional.equals({ a: 1, b: 2 })({ a: 1, b: 2 })).toBe(true)
})
3 changes: 2 additions & 1 deletion src/conditional/equals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Kind } from '..'
import { deepEqual } from '../_internal/deepEqual'

/**
* `_$equals` is a type-level function that takes in two types, `T` and `U`, and
Expand Down Expand Up @@ -78,4 +79,4 @@ export interface Equals extends Kind.Kind {
* ```
*/
export const equals = ((x: unknown) => (y: unknown) =>
x === y) as Kind._$reify<Equals>
deepEqual(x, y)) as Kind._$reify<Equals>
6 changes: 6 additions & 0 deletions src/loop/until.spec.ts → src/loop/until.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ type Until_Spec = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
>
]

it('should loop until the stopping predicate is satisfied', () => {
expect(
Loop.until(NaturalNumber.isGreaterThan(10))(NaturalNumber.increment)(0)
).toBe(11)
})
33 changes: 32 additions & 1 deletion src/loop/until.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $, Conditional, Kind, Type } from '..'
import { $, Conditional, Kind, Type, Function } from '..'

/**
* `_$until` is a type-level function that takes in a looping clause kind, an
Expand Down Expand Up @@ -92,3 +92,34 @@ export interface Until extends Kind.Kind {
x: Type._$cast<this[Kind._], Kind.Kind<(x: never) => boolean>>
): Until_T1<typeof x>
}

/**
* Given a stopping predicate, an updater, and an initial value, loop until we reach
* a value that satisfies the stopping predicate.
*
* @param {Kind.Kind<(x: never) => boolean>} p - The stopping predicate.
* @param {Kind.Kind<(x: never) => unknown>} u - The updater.
* @param {unknown} x - The initial value.
*
* @example
* ```ts
* import { Kind, NaturalNumber } from "hkt-toolbelt";
*
* const result = Kind.until(
* NaturalNumber.isGreaterThan(10),
* NaturalNumber.add(1),
* 0
* ) // 11
* ```
*/
export const until = ((p: Function.Function) =>
(u: Function.Function) =>
(x: unknown) => {
let value = x

while (!p(value as never)) {
value = u(value as never)
}

return value
}) as Kind._$reify<Until>
1 change: 1 addition & 0 deletions src/natural-number/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-by'
export * from './modulo'
export * from './multiply'
export * from './square'
export * from './subtract'
export * from './subtract-by'
export * from './to-list'
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ type IsGreaterThan_Spec = [
// @ts-expect-error
Test.Expect<$<$<NaturalNumber.IsGreaterThan, boolean>, 2>>
]

it('should return whether the second number is greater than the first', () => {
expect(NaturalNumber.isGreaterThan(3)(2)).toBe(false)
})
18 changes: 18 additions & 0 deletions src/natural-number/is-greater-than.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,21 @@ interface IsGreaterThan_T<A extends Number.Number> extends Kind.Kind {
export interface IsGreaterThan extends Kind.Kind {
f(x: Type._$cast<this[Kind._], Number.Number>): IsGreaterThan_T<typeof x>
}

/**
* Given two numbers, return whether the second number is greater than the
* first.
*
* @param {number} a - The first number.
* @param {number} b - The second number.
*
* @example
* ```ts
* import { NaturalNumber } from "hkt-toolbelt";
*
* const result = NaturalNumber.isGreaterThan(3)(2)
* // ^? false
* ```
*/
export const isGreaterThan = ((a: number) => (b: number) =>
b > a) as Kind._$reify<IsGreaterThan>
17 changes: 17 additions & 0 deletions src/natural-number/square.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { $, Test, NaturalNumber } from '..'

type Square_Spec = [
/**
* Can square a natural number.
*/
Test.Expect<$<NaturalNumber.Square, 42>, 1764>,

/**
* Squaring zero results in zero.
*/
Test.Expect<$<NaturalNumber.Square, 0>, 0>
]

it('should square a natural number', () => {
expect(NaturalNumber.square(42)).toBe(1764)
})
Loading

0 comments on commit 420f6ea

Please sign in to comment.