Skip to content

Instantly share code, notes, and snippets.

@mackwic
Last active November 4, 2019 08:16
Show Gist options
  • Save mackwic/301e0145b079465e95341c4bdd2ab1f2 to your computer and use it in GitHub Desktop.
Save mackwic/301e0145b079465e95341c4bdd2ab1f2 to your computer and use it in GitHub Desktop.
import { expect } from 'chai'
import { curry, isArity0, castToCurry20, castToCurry21 } from '../../src/domain/FunctionalProgramming'
describe("Domain | FunctionalProgramming", () => {
describe("#isArity0", () => {
it("should be true when function takes zero arguments", () => {
expect(isArity0(() => void 0)).to.be.true
})
})
describe("#curry", () => {
describe("arity 2", () => {
const add = (a, b) => `${a} + ${b}`
it("should be callable with all arguments at once", () => {
// arrange
const f1 = curry(add)
// act
const result = f1(1, 2)
// assert
expect(result).to.equal("1 + 2")
})
it("should be callable with argument used one at once", () => {
// arrange
let f1 = curry(add)
// act
f1 = castToCurry21(f1)
const f2 = f1(12)
if (typeof f2 == "string") { throw new Error("failure !") }
const result = f2(2)
// assert
expect(result).to.equal("12 + 2")
})
})
describe("arity 3", () => {
const add = (a, b, c) => `${a} + ${b} - ${c}`
it("should be callable with all arguments at once", () => {
// arrange
const f1 = curry(add)
// act
const result = f1(1, 2, 3)
// assert
expect(result).to.equal("1 + 2 - 3")
})
it("should be callable with 2 arguments used, then one", () => {
// arrange
const f1 = curry(add)
// act
const f2 = f1(12, 25)
if (typeof f2 == "string") { throw new Error("failure !") }
// if () { throw new Error("failure !") }
const result = f2(2)
// assert
expect(result).to.equal("12 + 25 - 2")
})
it("should be callable with 1 arguments used, then 2", () => {
// arrange
const f1 = curry(add)
// act
const f2 = f1(12)
if (typeof f2 == "string") { throw new Error("failure !") }
const result = f2(23, 2)
// assert
expect(result).to.equal("12 + 23 - 2")
})
it("should be callable with one argument used at once", () => {
// arrange
const f1 = curry(add)
// act
const f2 = f1(7)
if (typeof f2 == "string") { throw new Error("failure !") }
const f3 = f2(8)
if (typeof f3 == "string") { throw new Error("failure !") }
const result = f3(9)
// assert
expect(result).to.equal("7 + 8 - 9")
})
})
describe("arity 4", () => {
const add = (a, b, c, d) => `${a} + ${b} - ${c} * ${d}`
it("should be callable with all arguments at once", () => {
// arrange
const f1 = curry(add)
// act
const result = f1(1, 2, 3, 4)
// assert
expect(result).to.equal("1 + 2 - 3 * 4")
})
it("should be callable with arguments used two by two", () => {
// arrange
const f1 = curry(add)
// act
const f2 = f1(12, 25)
if (typeof f2 == "string") { throw new Error("failure !") }
const result = f2(3, 45)
// assert
expect(result).to.equal("12 + 25 - 3 * 45")
})
it("should be callable with arguments used one at one", () => {
// arrange
const f1 = curry(add)
// act
const f2 = f1(7)
if (typeof f2 == "string") { throw new Error("failure !") }
const f3 = f2(8)
if (typeof f3 == "string") { throw new Error("failure !") }
const result = f3(9)
// assert
expect(result).to.equal("7 + 8 - 9")
})
})
})
})
export function isArityN(n: number, f: Function): boolean {
return f.length == n
}
export const isArityNC = (curry(isArityN) as Curry21<number, Function, boolean>)
export const isArity0 = isArityNC(0)
export const isArity1 = isArityNC(1)
export const isArity2 = isArityNC(2)
export const isArity3 = isArityNC(3)
export const isArity4 = isArityNC(4)
export type Curry20<A, B, Ret> = (a: A, b: B) => Ret
export type Curry21<A, B, Ret> = (a: A) => (b: B) => Ret
export type Curry2<A, B, Ret> = Curry20<A, B, Ret> | Curry21<A, B, Ret>
export type Curry30<A, B, C, Ret> = (a: A, b: B, c: C) => Ret
export type Curry31<A, B, C, Ret> = (a: A, b: B) => (c: C) => Ret
export type Curry32<A, B, C, Ret> = (a: A) => (b: B, c: C) => Ret
export type Curry33<A, B, C, Ret> = (a: A) => (b: B) => (c: C) => Ret
export type Curry40<A, B, C, D, Ret> = (a: A, b: B, c: C, d: D) => Ret
export type Curry41<A, B, C, D, Ret> = (a: A, b: B, c: C) => (d: D) => Ret
export type Curry42<A, B, C, D, Ret> = (a: A, b: B) => (c: C) => (d: D) => Ret
export type Curry43<A, B, C, D, Ret> = (a: A) => (b: B) => (c: C) => (d: D) => Ret
export function curry<A, Ret>(f: (a: A) => Ret): (a: A) => Ret
export function curry<A, B, Ret>(fn: (a: A, b?: B) => Ret): (Curry21<A, B, Ret> | Curry20<A, B, Ret>)
export function curry<A, B, C, Ret>(fn: (a: A, b?: B, c?: C) => Ret): Curry30<A, B, C, Ret> | Curry31<A, B, C, Ret> | Curry32<A, B, C, Ret> | Curry33<A, B, C, Ret>
export function curry<A, B, C, D, Ret>(fn: (a: A, b?: B, c?: C, d?: D) => Ret): Curry40<A, B, C, D, Ret> | Curry41<A, B, C, D, Ret> | Curry42<A, B, C, D, Ret> | Curry43<A, B, C, D, Ret>
export function curry(fn) {
const argN = fn.length;
return function (...args) {
if (args.length < argN) {
return curry(fn.bind(this, ...args));
} else {
return fn.call(this, ...args);
}
}
}
export function tagCurry20<A, B, Ret>(f: Curry2<A, B, Ret>): f is Curry20<A, B, Ret> {
if (!isArity2(f)) { throw new Error("type tagging error !") }
return true
}
export function tagCurry21<A, B, Ret>(f: Curry2<A, B, Ret>): f is Curry21<A, B, Ret> {
if (!isArity1(f)) { throw new Error("type tagging error !") }
return true
}
export function castToCurry20<A, B, Ret>(f: Curry2<A, B, Ret>): Curry20<A, B, Ret> {
if (tagCurry20(f)) {
return f as Curry20<A, B, Ret>
}
}
export function castToCurry21<A, B, Ret>(f: Curry2<A, B, Ret>): Curry21<A, B, Ret> {
if (tagCurry21(f)) {
return f as Curry21<A, B, Ret>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment