diff --git a/src/core/index.ts b/src/core/index.ts index cd34f77..7d48aa8 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -3,6 +3,8 @@ export const isUndefined = (data: unknown): data is undefined => data === undefined +export const isNull = (data: unknown): data is null => data === null + export const isFunction = (data: unknown): data is Function => typeof data === "function" @@ -176,7 +178,7 @@ export function isPlainObject(obj: unknown) { export const let_ = ( expression: (...vars: Args) => R, -) => (vars: Args): R => expression(...vars) +) => (...vars: Args): R => expression(...vars) export const withDefault = (fallback: () => T) => ( value: T | undefined, diff --git a/src/index.ts b/src/index.ts index d054c7b..836f813 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from "./core/index" export * from "./memo/index" export * from "./monads/index" +export * from "./list/index" // interface Setoid { // equals(other: Setoid): boolean diff --git a/src/list/List.ts b/src/list/List.ts new file mode 100644 index 0000000..66a2d79 --- /dev/null +++ b/src/list/List.ts @@ -0,0 +1,72 @@ +import { let_, pipe } from "../core/index" + +export type Nil = undefined +export type Cons = [T, Cons | Nil] +export type List = Nil | Cons +export type NonEmptyList = Cons + +export const Nil = (): List => undefined +export const cons = (head: T) => (list: List): List => [head, list] +export const head = (list: List) => + list !== undefined ? list[0] : undefined +export const tail = (list: List) => + list !== undefined ? list[1] : undefined + +export const fromArray = (items: T[]) => + items.reverse().reduce((sum, item) => cons(item)(sum), Nil()) + +export const of = (...args: T[]) => fromArray(args) +export const empty = Nil + +export const map = (fn: (a: A) => B) => (list: List): List => + let_<[A | undefined, List], List>((h, t) => + h !== undefined ? [fn(h), map(fn)(t)] : Nil(), + )(head(list), tail(list)) + +export const filter = (fn: (a: A) => unknown) => (list: List): List => + let_<[A | undefined, List], List>((h, t) => + h !== undefined ? (fn(h) ? [h, filter(fn)(t)] : filter(fn)(t)) : Nil(), + )(head(list), tail(list)) + +export const some = (fn: (a: A) => unknown) => (list: List): boolean => + let_<[A | undefined, List], boolean>((h, t) => + h !== undefined ? (fn(h) ? true : some(fn)(t)) : false, + )(head(list), tail(list)) + +export const every = (fn: (a: A) => unknown) => (list: List): boolean => + let_<[A | undefined, List], boolean>((h, t) => + h !== undefined ? (!fn(h) ? false : every(fn)(t)) : true, + )(head(list), tail(list)) + +export const reduce = (fn: (acc: T, a: A) => T) => (acc: T) => ( + list: List, +): T => + let_<[A | undefined, List], T>((h, t) => + h !== undefined ? reduce(fn)(fn(acc, h))(t) : acc, + )(head(list), tail(list)) + +export const toArray = (list: List): T[] => + reduce((acc, item) => { + acc.push(item) + console.log("hey", item, acc) + + return acc + })([])(list) + +export const size = reduce((acc: number) => acc + 1)(0) + +export const conj = (item: A) => (list: List): List => + let_<[A | undefined, List], List>((h, t) => + h !== undefined ? cons(h)(conj(item)(t)) : of(item), + )(head(list), tail(list)) + +export const last = (list: List): A | undefined => + let_<[A | undefined, List], A | undefined>((h, t) => + h !== undefined ? (t !== undefined ? last(t) : h) : undefined, + )(head(list), tail(list)) + +export const reverse = (list: List) => + reduce, A>((acc, item) => cons(item)(acc))(Nil())(list) + +export const removeLast = (list: List): List => + pipe(reverse, tail, reverse)(list) diff --git a/src/list/__tests__/List.spec.ts b/src/list/__tests__/List.spec.ts new file mode 100644 index 0000000..d3fcccf --- /dev/null +++ b/src/list/__tests__/List.spec.ts @@ -0,0 +1,105 @@ +import { + head, + tail, + empty, + of, + fromArray, + toArray, + map, + filter, + some, + every, + size, + reverse, + cons, + conj, + reduce, + last, + removeLast, +} from "../List" + +describe("List", () => { + test("empty", () => { + expect(head(empty())).toBeUndefined() + expect(tail(empty())).toBeUndefined() + }) + + test("of", () => { + const hasFive = of(5) + expect(head(hasFive)).toBe(5) + expect(tail(hasFive)).toBeUndefined() + }) + + test("fromArray", () => { + const list = fromArray([1, 2, 3]) + expect(toArray(list)).toEqual([1, 2, 3]) + }) + + test("map", () => { + const list = fromArray([1, 2, 3]) + const mappedList = map((a: number) => a + 1)(list) + expect(toArray(mappedList)).toEqual([2, 3, 4]) + }) + + test("filter", () => { + const list = fromArray([1, 2, 3, 4]) + const mappedList = filter((a: number) => a % 2 === 0)(list) + expect(toArray(mappedList)).toEqual([2, 4]) + }) + + test("some", () => { + const list = fromArray([1, 2, 3, 4]) + + const someListA = some((a: number) => a > 3)(list) + expect(someListA).toBe(true) + + const someListB = some((a: number) => a < 0)(list) + expect(someListB).toBe(false) + }) + + test("every", () => { + const list = fromArray([1, 2, 3, 4]) + + const someListA = every((a: number) => a > 0)(list) + expect(someListA).toBe(true) + + const someListB = every((a: number) => a > 3)(list) + expect(someListB).toBe(false) + }) + + test("size", () => { + const list = fromArray([1, 2, 3, 4]) + expect(size(list)).toBe(4) + }) + + test("reduce", () => { + const list = fromArray([1, 2, 3]) + const sum = reduce((acc: number, a: number) => acc + a)(0)(list) + expect(sum).toBe(6) + }) + + test("cons", () => { + const list = fromArray([1, 2]) + expect(toArray(cons(0)(list))).toEqual([0, 1, 2]) + }) + + test("conj", () => { + const list = fromArray([1, 2]) + expect(toArray(conj(3)(list))).toEqual([1, 2, 3]) + }) + + test("last", () => { + const list = fromArray([1, 2, 3]) + expect(last(list)).toBe(3) + }) + + test("removeLast", () => { + const list = fromArray([1, 2, 3]) + expect(toArray(removeLast(list))).toEqual([1, 2]) + }) + + test("reverse", () => { + const list = fromArray([1, 2, 3, 4]) + expect(toArray(reverse(list))).toEqual([4, 3, 2, 1]) + }) +}) diff --git a/src/list/index.ts b/src/list/index.ts new file mode 100644 index 0000000..9ff6e8a --- /dev/null +++ b/src/list/index.ts @@ -0,0 +1 @@ +export * as List from "./List"