Skip to content
This repository has been archived by the owner on Apr 2, 2023. It is now read-only.

Commit

Permalink
refactor: rewrite memo internals more FP
Browse files Browse the repository at this point in the history
  • Loading branch information
tdreyno committed May 3, 2020
1 parent ae5c409 commit c8e50ca
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 193 deletions.
12 changes: 5 additions & 7 deletions docs/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,9 @@ Basically a functional switch statement. Run several predicates to find the corr
```typescript
const detectRange = cond(
[
[lessThan(0), () => "Negative"],
[lessThan(100), () => "Less than 100"]
],
() => "Greater than 100"
[lessThan(0), () => "Negative"],
[lessThan(100), () => "Less than 100"],
() => "Greater than 100",
)

detectRange(101)
Expand Down Expand Up @@ -453,7 +451,7 @@ isBetween5And10Inclusive(1)
type isBetween = (
a: number,
b: number,
inclusive?: boolean
inclusive?: boolean,
) => (data: number) => boolean
```
Expand Down Expand Up @@ -517,7 +515,7 @@ Create a function runs a series of functions in order, passing the result of eac
const incrementToStringAndRepeat = pipe(
(a: number) => a + 1,
(b: number) => b.toString(),
(c: string) => c + c
(c: string) => c + c,
)

incrementToStringAndRepeat(100)
Expand Down
19 changes: 2 additions & 17 deletions src/core/__tests__/cond.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
import { cond, equals, lessThanEquals, constant } from "../index"

describe("cond", () => {
test("creates a function which evaluates some conditions", () => {
const searchNumbers = cond([
test("creates a function which evaluates some conditions with a fallback", () => {
const searchNumbers = cond(
[lessThanEquals(5), constant("<=5")],
[lessThanEquals(10), constant("6-10")],
[equals(11), constant("11")],
])

expect(searchNumbers(0)).toBe("<=5")
expect(searchNumbers(7)).toBe("6-10")
expect(searchNumbers(11)).toBe("11")
expect(searchNumbers(Infinity)).toBe(undefined)
})

test("creates a function which evaluates some conditions with a fallback", () => {
const searchNumbers = cond(
[
[lessThanEquals(5), constant("<=5")],
[lessThanEquals(10), constant("6-10")],
[equals(11), constant("11")],
],
constant("unknown"),
)

Expand Down
132 changes: 112 additions & 20 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

export const isUndefined = (data: unknown): data is undefined =>
data === undefined

export const isFunction = (data: unknown): data is Function =>
typeof data === "function"

export const index = (i: number) => <T>(items: T[]): T | undefined => items[i]

export const nth = (n: number) => index(n - 1)
Expand All @@ -12,6 +19,9 @@ export const rest = <T>([, ...remaining]: T[]) => remaining
export const constant = <T>(value: T) => () => value

export const identity = <T>(value: T) => value
export const knownIdentity = <T>() => (value: unknown) => value as T
export const known = <T, U>(fn: (value: T) => U | undefined) => (value: T): U =>
fn(value) as U

export const not = <Args extends any[], T>(fn: (...args: Args) => T) => (
...args: Args
Expand All @@ -31,13 +41,21 @@ export const lessThan = (value: number) => (data: number): boolean =>
export const lessThanEquals = (value: number) => (data: number): boolean =>
data <= value

export const or = <Args extends any[]>(
...options: Array<(...args: Args) => unknown>
) => (...args: Args): boolean => options.some(fn => fn(...args))
export const or = <T>(...options: Array<((value: T) => unknown) | unknown>) => (
value: T,
): boolean =>
options.some(fnOrBool => (isFunction(fnOrBool) ? fnOrBool(value) : fnOrBool))

export const and = <Args extends any[]>(
...options: Array<(...args: Args) => unknown>
) => (...args: Args): boolean => options.every(fn => fn(...args))
...options: Array<((...args: Args) => unknown) | unknown>
) => (...args: Args): boolean =>
options.every(fnOrBool =>
isFunction(fnOrBool)
? fnOrBool(...args)
: options.every(fnOrBool =>
isFunction(fnOrBool) ? fnOrBool(...args) : fnOrBool,
),
)

export const isBetween = (a: number, b: number, inclusive = false) =>
inclusive
Expand Down Expand Up @@ -68,6 +86,7 @@ export function pipe<A, B, C>(
a: (data: A) => B,
b: (data: B) => C,
): (data: A) => C
export function pipe<A, B>(a: (data: A) => B): (data: A) => B
export function pipe(...fns: ((data: any) => any)[]) {
return (data: any) => fns.reduce((sum, fn) => fn(sum), data)
}
Expand All @@ -84,32 +103,59 @@ export function apply<T>(fn: (...args: any[]) => T): (args: any) => T {
export const map = <T, U>(fn: (a: T) => U) => (data: T[]): U[] => data.map(fn)
export const chain = <T, U>(fn: (a: T) => U) => (data: T): U => fn(data)

type Predicate<T> = (data: T) => unknown
type Predicate<T> = unknown | ((data: T) => unknown)
type Transform<T, U> = (data: T) => U
type Condition<T, U> = [Predicate<T>, Transform<T, U>]

export function cond<T, U>(
conditions: Condition<T, U>[],
): (data: T) => U | undefined
a: Condition<T, U>,
b: Condition<T, U>,
c: Condition<T, U>,
d: Condition<T, U>,
orElse: Transform<T, U>,
): (data: T) => U
export function cond<T, U>(
conditions: Condition<T, U>[],
a: Condition<T, U>,
b: Condition<T, U>,
c: Condition<T, U>,
d: Condition<T, U>,
e: Condition<T, U>,
orElse: Transform<T, U>,
): (data: T) => U
export function cond<T, U>(
conditions: Condition<T, U>[],
orElse?: Transform<T, U>,
): (data: T) => U | undefined {
return (data: T) => {
const found = conditions.find(([predicate]) => predicate(data))

if (found) {
return found[1](data)
}
a: Condition<T, U>,
b: Condition<T, U>,
c: Condition<T, U>,
orElse: Transform<T, U>,
): (data: T) => U
export function cond<T, U>(
a: Condition<T, U>,
b: Condition<T, U>,
orElse: Transform<T, U>,
): (data: T) => U
export function cond<T, U>(
a: Condition<T, U>,
orElse: Transform<T, U>,
): (data: T) => U
export function cond<T, U>(orElse: Transform<T, U>): (data: T) => U
export function cond<T, U>(
...conditions: (Condition<T, U> | Transform<T, U>)[]
): (data: T) => U {
const orElse = conditions.pop() as Transform<T, U>

return (data: T): U => {
const found = (conditions as Condition<T, U>[]).find(([predicate]) =>
isFunction(predicate) ? predicate(data) : data,
)

return orElse ? orElse(data) : undefined
return found ? found[1](data) : orElse(data)
}
}

export const if_ = <T, R>(truthy: (value: T) => R, falsey: (value: T) => R) => (
value: T,
): R => (value ? truthy(value) : falsey(value))

export function isPlainObject(obj: unknown) {
// Basic check for Type object that's not null
if (typeof obj == "object" && obj !== null) {
Expand All @@ -128,4 +174,50 @@ export function isPlainObject(obj: unknown) {
return false
}

export const isUndefined = (data: unknown) => data === undefined
export const let_ = <Args extends any[], R>(
expression: (...vars: Args) => R,
) => (vars: Args): R => expression(...vars)

export const withDefault = <T>(fallback: () => T) => (
value: T | undefined,
): T => (isUndefined(value) ? fallback() : value) as Exclude<T, undefined>

export const isA = <K extends Function>(klass: K) => (
value: unknown,
): value is K => value instanceof klass

export const isArray_ = <T extends any[]>(value: unknown): value is T =>
Array.isArray(value)

export type Recur<Args extends any[]> = {
type: "recur"
args: Args
}

const isRecur = <Args extends any[]>(
data: Recur<Args> | any,
): data is Recur<Args> => data && data.type === "recur"

export const recur = <Args extends any[]>(...args: Args): Recur<Args> => ({
type: "recur",
args,
})

export const loop = <Args extends any[], R>(
fn: (
recur_: (...args: Args) => Recur<Args>,
...args: Args
) => R | Recur<Args>,
) => (...args: Args): R => {
let lastArgs: Args = args
while (true) {
const result = fn(recur as (...args: Args) => Recur<Args>, ...lastArgs)

if (isRecur(result)) {
lastArgs = (result.args as unknown) as Args
continue
}

return result
}
}
44 changes: 44 additions & 0 deletions src/memo/TreeNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
export type UnbrandedTreeNode<K, V> = [
V | undefined,
Map<K, TreeNode<K, V>> | null,
]

export interface TreeNode<K, V> {
__brand: "TreeNode"
__keyType: K
__valueType: V
}

function brand<K, V>(unbranded: UnbrandedTreeNode<K, V>): TreeNode<K, V> {
return (unbranded as unknown) as TreeNode<K, V>
}

function unbrand<K, V>(branded: TreeNode<K, V>): UnbrandedTreeNode<K, V> {
return (branded as unknown) as UnbrandedTreeNode<K, V>
}

export const make = <K, V>(): TreeNode<K, V> =>
brand([undefined, new Map<unknown, TreeNode<K, V>>()] as UnbrandedTreeNode<
K,
V
>)

export const value = <V>(node: TreeNode<unknown, V>) => unbrand(node)[0]

export const setValue = <V>(value: V, node: TreeNode<unknown, V>) =>
(unbrand(node)[0] = value)

export const isEmpty = (node: TreeNode<unknown, unknown>) => !unbrand(node)[1]

export const has = <K>(key: K, node: TreeNode<K, unknown>) =>
isEmpty(node) ? false : unbrand(node)[1]!.has(key)

export const get = <K, K2, V>(key: K2, node: TreeNode<K, V>) =>
isEmpty(node) ? undefined : unbrand(node)[1]!.get((key as unknown) as K)

export const set = <K, V>(
key: K,
value: TreeNode<K, V>,
node: TreeNode<K, V>,
) => (isEmpty(node) ? null : unbrand(node)[1]!.set(key, value))
Loading

0 comments on commit c8e50ca

Please sign in to comment.