334 lines
8.8 KiB
TypeScript
334 lines
8.8 KiB
TypeScript
import * as F from "effect/Function"
|
|
import assert from "node:assert/strict"
|
|
import { describe, it } from "vitest"
|
|
|
|
describe("Function", () => {
|
|
describe("dual", () => {
|
|
it("arity 2 - data-first", () => {
|
|
const sum = F.dual<
|
|
(that: number) => (self: number) => number,
|
|
(self: number, that: number) => number
|
|
>(2, (self: number, that: number) => self + that)
|
|
assert.strictEqual(sum(2, 3), 5)
|
|
})
|
|
|
|
it("arity 2 - data-last", () => {
|
|
const sum = F.dual<
|
|
(that: number) => (self: number) => number,
|
|
(self: number, that: number) => number
|
|
>(2, (self: number, that: number) => self + that)
|
|
assert.strictEqual(F.pipe(2, sum(3)), 5)
|
|
})
|
|
|
|
it("arity 3 - data-first", () => {
|
|
const add3 = F.dual<
|
|
(b: number, c: number) => (a: number) => number,
|
|
(a: number, b: number, c: number) => number
|
|
>(3, (a: number, b: number, c: number) => a + b + c)
|
|
assert.strictEqual(add3(1, 2, 3), 6)
|
|
})
|
|
|
|
it("arity 3 - data-last", () => {
|
|
const add3 = F.dual<
|
|
(b: number, c: number) => (a: number) => number,
|
|
(a: number, b: number, c: number) => number
|
|
>(3, (a: number, b: number, c: number) => a + b + c)
|
|
assert.strictEqual(F.pipe(1, add3(2, 3)), 6)
|
|
})
|
|
|
|
it("arity 4 (default branch) - data-first", () => {
|
|
const add4 = F.dual(4, (a: number, b: number, c: number, d: number) => a + b + c + d)
|
|
assert.strictEqual(add4(1, 2, 3, 4), 10)
|
|
})
|
|
|
|
it("arity 4 (default branch) - data-last", () => {
|
|
const add4 = F.dual(4, (a: number, b: number, c: number, d: number) => a + b + c + d)
|
|
assert.strictEqual(F.pipe(1, add4(2, 3, 4)), 10)
|
|
})
|
|
|
|
it("throws RangeError for arity 0", () => {
|
|
assert.throws(() => F.dual(0 as any, () => {}), RangeError)
|
|
})
|
|
|
|
it("throws RangeError for arity 1", () => {
|
|
assert.throws(() => F.dual(1 as any, (_a: any) => {}), RangeError)
|
|
})
|
|
|
|
it("predicate-based - data-first", () => {
|
|
const sum = F.dual<
|
|
(that: number) => (self: number) => number,
|
|
(self: number, that: number) => number
|
|
>(
|
|
(args) => args.length === 2,
|
|
(self: number, that: number) => self + that
|
|
)
|
|
assert.strictEqual(sum(2, 3), 5)
|
|
})
|
|
|
|
it("predicate-based - data-last", () => {
|
|
const sum = F.dual<
|
|
(that: number) => (self: number) => number,
|
|
(self: number, that: number) => number
|
|
>(
|
|
(args) => args.length === 2,
|
|
(self: number, that: number) => self + that
|
|
)
|
|
assert.strictEqual(F.pipe(2, sum(3)), 5)
|
|
})
|
|
})
|
|
|
|
describe("apply", () => {
|
|
it("applies a value to a function", () => {
|
|
const len = (s: string) => s.length
|
|
assert.strictEqual(F.apply("hello")(len), 5)
|
|
})
|
|
|
|
it("works with pipe", () => {
|
|
const double = (n: number) => n * 2
|
|
assert.strictEqual(F.pipe(double, F.apply(3)), 6)
|
|
})
|
|
})
|
|
|
|
describe("identity", () => {
|
|
it("returns the same value for primitives", () => {
|
|
assert.strictEqual(F.identity(5), 5)
|
|
assert.strictEqual(F.identity("hello"), "hello")
|
|
assert.strictEqual(F.identity(true), true)
|
|
assert.strictEqual(F.identity(null), null)
|
|
assert.strictEqual(F.identity(undefined), undefined)
|
|
})
|
|
|
|
it("returns the same reference for objects", () => {
|
|
const obj = { a: 1 }
|
|
assert.strictEqual(F.identity(obj), obj)
|
|
})
|
|
})
|
|
|
|
describe("satisfies", () => {
|
|
it("returns value unchanged", () => {
|
|
assert.strictEqual(F.satisfies<number>()(5), 5)
|
|
})
|
|
})
|
|
|
|
describe("cast", () => {
|
|
it("returns value unchanged at runtime", () => {
|
|
assert.strictEqual(F.cast(42), 42)
|
|
})
|
|
})
|
|
|
|
describe("constant", () => {
|
|
it("returns the same value every time", () => {
|
|
const c = F.constant(42)
|
|
assert.strictEqual(c(), 42)
|
|
assert.strictEqual(c(), 42)
|
|
})
|
|
|
|
it("returns reference-equal objects", () => {
|
|
const obj = { a: 1 }
|
|
const c = F.constant(obj)
|
|
assert.strictEqual(c(), obj)
|
|
assert.strictEqual(c() === c(), true)
|
|
})
|
|
})
|
|
|
|
describe("constTrue", () => {
|
|
it("returns true", () => {
|
|
assert.strictEqual(F.constTrue(), true)
|
|
})
|
|
})
|
|
|
|
describe("constFalse", () => {
|
|
it("returns false", () => {
|
|
assert.strictEqual(F.constFalse(), false)
|
|
})
|
|
})
|
|
|
|
describe("constNull", () => {
|
|
it("returns null", () => {
|
|
assert.strictEqual(F.constNull(), null)
|
|
})
|
|
})
|
|
|
|
describe("constUndefined", () => {
|
|
it("returns undefined", () => {
|
|
assert.strictEqual(F.constUndefined(), undefined)
|
|
})
|
|
})
|
|
|
|
describe("constVoid", () => {
|
|
it("returns undefined", () => {
|
|
assert.strictEqual(F.constVoid(), undefined)
|
|
})
|
|
})
|
|
|
|
describe("flip", () => {
|
|
it("reverses the order of curried arguments", () => {
|
|
const f = (a: number) => (b: string) => a - b.length
|
|
assert.strictEqual(F.flip(f)("aaa")(2), -1)
|
|
})
|
|
})
|
|
|
|
describe("compose", () => {
|
|
const increment = (n: number) => n + 1
|
|
const double = (n: number) => n * 2
|
|
|
|
it("data-first: composes left-to-right", () => {
|
|
assert.strictEqual(F.compose(increment, double)(2), 6)
|
|
})
|
|
|
|
it("data-last: composes with pipe", () => {
|
|
assert.strictEqual(F.pipe(increment, F.compose(double))(2), 6)
|
|
})
|
|
})
|
|
|
|
describe("absurd", () => {
|
|
it("throws an error when called", () => {
|
|
assert.throws(() => F.absurd(undefined as never), {
|
|
message: "Called `absurd` function which should be uncallable"
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("tupled", () => {
|
|
it("converts a multi-arg function to accept a tuple", () => {
|
|
const add = (a: number, b: number) => a + b
|
|
const addTupled = F.tupled(add)
|
|
assert.strictEqual(addTupled([1, 2]), 3)
|
|
})
|
|
})
|
|
|
|
describe("untupled", () => {
|
|
it("converts a tuple-accepting function to multi-arg", () => {
|
|
const first = (tuple: [number, string]) => tuple[0]
|
|
const firstUntupled = F.untupled(first)
|
|
assert.strictEqual(firstUntupled(1, "a"), 1)
|
|
})
|
|
})
|
|
|
|
describe("pipe", () => {
|
|
it("returns value with no functions", () => {
|
|
assert.strictEqual(F.pipe(5), 5)
|
|
})
|
|
|
|
it("applies one function", () => {
|
|
assert.strictEqual(F.pipe(5, (n) => n + 1), 6)
|
|
})
|
|
|
|
it("applies two functions", () => {
|
|
assert.strictEqual(F.pipe(5, (n) => n + 1, (n) => n * 2), 12)
|
|
})
|
|
|
|
it("applies a long chain", () => {
|
|
assert.strictEqual(
|
|
F.pipe(
|
|
1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1
|
|
),
|
|
10
|
|
)
|
|
})
|
|
|
|
it("transforms types through the chain", () => {
|
|
const result = F.pipe(
|
|
5,
|
|
(n) => n.toString(),
|
|
(s) => s.length
|
|
)
|
|
assert.strictEqual(result, 1)
|
|
})
|
|
})
|
|
|
|
describe("flow", () => {
|
|
it("single function returns it unchanged", () => {
|
|
const f = (n: number) => n + 1
|
|
assert.strictEqual(F.flow(f)(1), 2)
|
|
})
|
|
|
|
it("composes two functions", () => {
|
|
const len = (s: string) => s.length
|
|
const double = (n: number) => n * 2
|
|
assert.strictEqual(F.flow(len, double)("aaa"), 6)
|
|
})
|
|
|
|
it("composes three functions", () => {
|
|
assert.strictEqual(
|
|
F.flow(
|
|
(s: string) => s.length,
|
|
(n) => n * 2,
|
|
(n) => n + 1
|
|
)("ab"),
|
|
5
|
|
)
|
|
})
|
|
|
|
it("composes up to nine functions", () => {
|
|
const result = F.flow(
|
|
(n: number) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1,
|
|
(n) => n + 1
|
|
)(0)
|
|
assert.strictEqual(result, 9)
|
|
})
|
|
|
|
it("first function can accept multiple arguments", () => {
|
|
const f = F.flow(
|
|
(a: number, b: number) => a + b,
|
|
(n) => n * 2
|
|
)
|
|
assert.strictEqual(f(1, 2), 6)
|
|
})
|
|
})
|
|
|
|
describe("hole", () => {
|
|
it("throws when called", () => {
|
|
assert.throws(() => F.hole<string>())
|
|
})
|
|
})
|
|
|
|
describe("SK", () => {
|
|
it("returns the second argument", () => {
|
|
assert.strictEqual(F.SK(0, "hello"), "hello")
|
|
assert.strictEqual(F.SK("a", 42), 42)
|
|
})
|
|
})
|
|
|
|
describe("memoize", () => {
|
|
it("caches results for the same object reference", () => {
|
|
let callCount = 0
|
|
const f = F.memoize((obj: { n: number }) => {
|
|
callCount++
|
|
return obj.n * 2
|
|
})
|
|
const key = { n: 5 }
|
|
assert.strictEqual(f(key), 10)
|
|
assert.strictEqual(f(key), 10)
|
|
assert.strictEqual(callCount, 1)
|
|
})
|
|
|
|
it("recomputes for different object references", () => {
|
|
let callCount = 0
|
|
const f = F.memoize((obj: { n: number }) => {
|
|
callCount++
|
|
return obj.n * 2
|
|
})
|
|
assert.strictEqual(f({ n: 5 }), 10)
|
|
assert.strictEqual(f({ n: 5 }), 10)
|
|
assert.strictEqual(callCount, 2)
|
|
})
|
|
})
|
|
})
|