Skip to content

Instantly share code, notes, and snippets.

@ElectricCoffee
Last active January 19, 2023 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ElectricCoffee/915817349cca27aebe3e3a2a71da5549 to your computer and use it in GitHub Desktop.
Save ElectricCoffee/915817349cca27aebe3e3a2a71da5549 to your computer and use it in GitHub Desktop.
import _ from 'lodash';
import Fn from './functools';
it('ignores all inputs', () => {
const actual = Fn.nop(800, 'hello there', []);
expect(actual).toBeUndefined();
});
it('only returns the constant', () => {
const fn = Fn.constant(8);
const expected = [8, 8, 8, 8];
const actual = [1, 2, 3, 4].map(fn);
expect(actual).toEqual(expected);
});
it('flips the inputs correctly', () => {
const fn = Fn.flip(Fn.num.sub);
const expected = 2 - 5;
const actual = fn(5, 2);
expect(actual).toBe(expected);
});
it('applies left arg correctly', () => {
const fn = Fn.overLeft(Fn.str.cat, x => x.toUpperCase());
const expected = 'BATman';
const actual = fn('bat', 'man');
expect(actual).toEqual(expected);
});
it('applies right arg correctly', () => {
const fn = Fn.overRight(Fn.str.cat, JSON.stringify);
const expected = 'foo: ["bar"]';
const actual = fn('foo: ', ['bar']);
expect(actual).toEqual(expected);
});
it('applies both args correctly', () => {
const fn = Fn.over(Fn.num.add, Number);
const expected = 24;
const actual = fn('6', '18');
expect(actual).toBe(expected);
});
it('correctly applies two different functions to the same argument', () => {
const avg = Fn.fork1(Fn.num.div, _.sum, _.size);
const expected = (1 + 2 + 3) / 3; // in case of rounding errors;
const actual = avg([1, 2, 3]);
expect(actual).toBe(expected);
});
it('correctly applies two different functions to both arguments', () => {
const fn = Fn.fork2(Fn.num.div, Fn.num.add, Fn.num.mul);
const expected = (2 + 3) / (2 * 3);
const actual = fn(2, 3);
expect(actual).toBe(expected);
});
import _ from 'lodash';
/* eslint-disable no-bitwise */
namespace Fn {
/** No operation. A function that does nothing */
export const nop = (..._ignore: any) => {};
/** Identity function. Returns its own input */
export const id = <T>(x: T) => x;
/** creates a function that returns a constant value `x` regardless of input */
export const constant =
<T>(x: T) =>
(..._ignore: any) =>
x;
/** Constructs a new binary function with its arguments flipped. `f(a, b)` becomes `f'(b, a)` */
export const flip =
<A, B, C>(f: (a: A, b: B) => C) =>
(b: B, a: A) =>
f(a, b);
/**
* Constructs a new function `f'(a, b)` which does the same as calling `f(g(a), b)`.
* It applies `g` to its left argument before running `f`
*/
export const overLeft =
<A, B, C, D>(f: (c: C, b: B) => D, g: (a: A) => C) =>
(a: A, b: B) =>
f(g(a), b);
/**
* Constructs a new function `f'(a, b)` which does the same as calling `f(a, g(b))`.
* It applies `g` to its right argument before running `f`
*/
export const overRight =
<A, B, C, D>(f: (a: A, c: C) => D, g: (b: B) => C) =>
(a: A, b: B) =>
f(a, g(b));
/**
* Constructs a new function `f'(a, b)` which does the same as calling `f(g(a), g(b))`.
* It applies `g` to both arguments before running `f`.
* Essentially the opposite of `atop`.
*/
export const over =
<AB, C, D>(f: (c1: C, c2: C) => D, g: (ab: AB) => C) =>
(a: AB, b: AB) =>
f(g(a), g(b));
/**
* Constructs a new function `f'(a, b)` which does the same as calling `g(f(a, b))`.
* It applies `a` and `b` to `f` before calling `g`.
* Essentially the opposite of `over`.
*/
export const atop =
<A, B, C, D>(f: (a: A, b: B) => C, g: (c: C) => D) =>
(a: A, b: B) =>
g(f(a, b));
/**
* Creates a new function `f'(a)` which does the same as running `f(g(a), h(a))`.
* It runs `a` through `g` and `h` before running the results through `f`.
*/
export const fork1 =
<A, B, C, D>(f: (b: B, c: C) => D, g: (a: A) => B, h: (a: A) => C) =>
(a: A) =>
f(g(a), h(a));
export const compose = _.flow;
/**
* Creates a new function `f'(a, b)` which does the same as running `f(g(a, b), h(a, b))`.
* It runs `a` and `b` through `g` and `h` and feeds the results to `f`.
*/
export const fork2 =
<A, B, C, D, E>(
f: (c: C, d: D) => E,
g: (a: A, b: B) => C,
h: (a: A, b: B) => D,
) =>
(a: A, b: B) =>
f(g(a, b), h(a, b));
// a lot of the more advanced functions require binary functions of the form foo(a, b).
// None of the built-in binary operators follow this scheme.
export namespace num {
export const neg = (a: number) => -a;
export const add = (a: number, b: number) => a + b;
export const sub = (a: number, b: number) => a - b;
export const mul = (a: number, b: number) => a * b;
export const div = (a: number, b: number) => a / b;
}
export namespace bool {
export const not = (a: boolean) => !a;
export const and = (a: boolean, b: boolean) => a && b;
export const or = (a: boolean, b: boolean) => a || b;
}
export namespace bin {
export const not = (a: number) => ~a;
export const and = (a: number, b: number) => a & b;
export const or = (a: number, b: number) => a | b;
}
export namespace str {
export const cat = (a: string, b: string) => a + b;
}
}
export default Fn;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment