Skip to content

dovca/fp-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

88 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

What is FP-JS about?

The goal of this package is to provide a set of functions whose combinations can be used to write any synchronous JS logic. These functions are written without any keywords; builtin objects; string, number or array literals; external dependencies using as few JS operators as possible.

JavaScript operators currently used: ~, +, <, =, (), ===.

Which is only 10.17% of all the operators that Javascript offers! Moreover, operators ~, +, <, === are each used only once in the whole code.

FP-JS does not depend on a browser environment, you can use it with Node.js.

How do I use it?

  1. Download and install the package
    npm i --save @dovca/fp
  2. Require the functions in your script
    const {$,_,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z} = require('@dovca/fp');

What does it contain?

This package provides 28 single-letter-named functions:

$ - Wrap

This function has two signatures and behaviors:

  • $(A)

    If called with only one argument , $(A) returns a function that returns the value A.

  • $(F, ...A)

    If called with more than one argument, $(F, ...A) returns a function that returns the value of arguments ...A passed through function F, that is F(...A).

Examples:

$() // -> () => undefined
$(42) // -> () => 42
$((A, B) => A.concat(B), 'foo', 'bar') // -> () => 'foo'.concat('bar') 

_ - Identity

_(A) returns whatever was passed into it, that means _(A) === A.

Examples:

_() // -> undefined
_(42) // -> 42

const a = {foo: 'bar'};
_(a) // -> {foo: 'bar'} which is strictly equal to `a`

a - Add

a(A, B) returns the sum of its arguments. If argument A is omitted, it defaults to 0. If argument B is omitted, it defaults to 1.

Examples:

a() // -> 1
a(42) // -> 43
a(2, 3) // -> 5
a(10, -1) // -> 9

b - Subtract

b(A, B) subtracts B from A and returns the result. If argument A is omitted, it defaults to 0. If argument B is omitted, it defaults to 1.

Examples:

b() // -> -1
b(5) // -> 4
b(5, 3) // -> 2
b(1, -1) // -> 2

c - Count

c(A) returns:

  • The number of items in array A before the first undefined item or empty index if A is an array
  • The number of characters in string A if A is a string

Examples:

c([]) // -> 0
c(['foo', 'bar']) // -> 2
c([, , 42]) // -> 0
c([1, 1, , , 3, 3]) // -> 2
c('') // -> 0
c('donkey') // -> 6

d - Double

d(A) returns the value of A multiplied by 2. If argument A is omitted, it defaults to 1.

Examples:

d() // -> 2
d(42) // -> 84
d(-10) // -> -20

e - Compare equal

e(A, B) returns true iff A is strictly equal to B, that is A === B. See this MDN page for details.

Examples:

e() // -> true (both arguments are undefined, thus equal to each other)
e(1, 1) // -> true
e(1, '1') // -> false
e({foo: 'bar'}, {foo: 'bar'}) // -> false (actually different objects that just look the same)

f - Filter

f(F, ...A) returns an array filled with elements X from ...A for which F(X) returns a truthy value. The returned elements are in the same relative order. If no elements are given, an empty array is returned.

Examples:

f((X) => true) // -> [] (no elements to filter, no elements returned)
f((X) => X > 10, 4, 7, 20, 12, 3, 50, 4, 7, 98) // -> [20, 12, 50, 98]
f((X) => X % 2 === 0, 1, 2, 3, 4, 5, 6) // -> [2, 4, 6]
f((X) => X.length > 5, 'foo', 'bar', 'hello', 'donkey') // -> ['donkey']

g - Compose

g(...F) returns a composition of functions ...F, that means g(A, B, C) returns (X) => A(B(C(X))).

Examples:

g(d, a, d, a)(9) // -> 42 (equals to (((9+1)*2)+1)*2 using functions `a` and `d` from this package)
g((A) => A.shift(), (A) => A.reverse(), (A) => A.split(''))('foobar') // -> 'r'

h - Switch

h(...C) behaves similarly to the native switch statement. Each argument must be an array of two functions [E, B], where E is the case expression and B is the case body. A case expression must return a truthy value in order for its case body to be executed. Only one and the first of all case bodies is executed. After doing so, any further case expressions are left unevaluated and the function h returns. The value returned from h() is the return value of the executed case body or undefined if no case body has been executed.

Examples:

const value = 42;
h(
	[() => value < 0, () => 'negative'],
	[() => value > 0, () => 'positive'],
	[() => true, () => 'zero'], //default case
) // -> 'positive'

i - If/else

i(C, T, F) behaves like the native if/else statement. It calls function T if the call C() returns a truthy value. If not, function F is called instead. The call to function i returns whatever T() or F() returns. If argument F is omitted, it defaults to a function that returns undefined.

Examples:

i(() => true, () => 'foo', () => 'bar') // -> 'foo'

const password = 'donkey';
i(() => password.length > 8, () => 'Good.', () => 'Too short.') // -> 'Too short.'

i(() => 69 < 42, () => 420) // -> undefined (no false branch supplied)

j - Create object

j(...P) constructs an object from key-value pairs defined by arguments ...P. Each argument must be an array [K, V] where K is a valid object key name and V is the value to be associated with key K. If no arguments are given, the call j() returns an empty object.

Examples:

j() // -> {}
j(['foo', 'bar']) // -> {foo: 'bar'}
j([1, 2], ['a-b-c', [1, 2, 3]]) // -> {1: 2, 'a-b-c': [1, 2, 3]}

k - If/else not empty

k(A, T, F) is a specialized version of the function i from this package. It calls T or F if the array or string A is (non-)empty respectively.

Note: Due to the very limited resources used in this package, k([undefined], () => true, () => false) unexpectedly returns false even if the array is obviously not empty.

Examples:

k([], () => 'foo', () => 'bar') // -> 'bar'
k([10, 20, 30], () => true, () => false) // -> true
k('', () => 'bar', () => 'baz') // -> 'baz'
k('hello world', () => 'hello', () => 'world') // -> 'hello'

l - Compare less

l(A, B) returns true if A is smaller than B, false otherwise. If argument B is omitted, it defaults to 0.

Examples:

l() // -> false (undefined is not smaller than 0)
l(0) // -> false
l(-5) // -> true
l(42) // -> false
l(1, 2) // -> true
l(2, 1) // -> false

m - Map

m(F, ...A) returns an array of arguments ...A passed through function F each individually.

Examples:

m((X) => X + 1, 0, 1, 2, 3) // -> [1, 2, 3, 4]
m(d, 1, 2, 3) // -> [2, 4, 6] (using function `d` from this package)
m(l, -4, 1, -10, 42) // -> [true, false, true, false] (using function `l` from this package)

n - Logical negation

n(A) returns the logical negation of A. Only works with numbers, booleans, undefined and null

Examples:

n() // -> true (negation of undefined)
n(true) // -> false
n(33) // -> false
n(-10) // -> false
n(0) // -> true
n(null) // -> true

o - Logical OR

o(A, B) returns A OR B. If argument B is omitted, it defaults to A

Examples:

o() // -> false (undefined converted to boolean)
o(42) // -> true
o(true, false) // -> true
o(false, false) // -> false
o(-5, 20) // -> true

p - Pipe

p(...F) returns a composition of functions ...F in reversed order, that means p(A, B, C) returns (X) => C(B(A(X))).

Examples:

p(d, a, d, a)(9) // -> 39 (equals to (((9*2)+1)*2)+1 using functions `a` and `d` from this package)
p((A) => A.split(''), (A) => A.reverse(), (A) => A.shift())('foobar') // -> 'r'

q - Dequeue

q(A) returns a copy of array A with the first element removed.

Examples:

q([1, 2, 3]) // -> [2, 3]
q(['foo']) // -> []
q([]) // -> []

r - Reduce

r(F, ...A) applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value. Last element of ...A is the initial accumulator value. If ...A contains only a single value, it is returned unmodified and F is not called. If ...A is empty, undefined is returned.

Examples:

r(a, 1, 2, 3, 4, 5) // -> 15 (using function `a` from this package)
r((S, V) => Math.max(S, V), -2, 0, 15, -9, 42, 8, -40) // -> 42 (finds the largest value)
r((A, B) => `${A} ${B}`, 'foo', 'bar', 'baz'); // -> 'foo bar baz'

r(a, 3) // -> 3 (nothing to reduce, this only initialized the accumulator)
r(a) // -> undefined (the accumulator wasn't even initialized)
r() // -> undefined (duh...)

s - String

s(...A) returns all arguments concatenated in a single string. If no arguments are given, s() returns an empty string.

Examples:

s() // -> ''
s('abc') // -> 'abc'
s(1, 2, 3) // -> '123'
s(true, 'foo', null, 3, 'bar') // -> 'truefoonull3bar'

t - Concatenate

t(A, ...B) returns a copy of array A with elements ...B appended to it.

Examples:

t([]) // -> []
t([1]) // -> [1]
t([1], 10) // -> [1, 10]
t([1, 2], 3, 4) // -> [1, 2, 3, 4]
t([1, 2], [3, 4]) // -> [1, 2, [3, 4]]

u - Curry

u(F, N) returns a curried N-ary function F. If argument N is omitted, it defaults to 2 making it easy to curry binary functions.

Examples:

const add = u((A, B) => A + B); // -> (A) => (B) => A + B
add(1)(2) // -> 3
add(12)(34) // -> 46

const addTwo = add(2); // -> (B) => 2 + B
addTwo(1) // -> 3
addTwo(42) // -> 44

const xyz = u((X, Y, Z) => X + Y * Z, 3);
xyz(2)(3)(4) // -> 14
xyz(2)(3)(10) // -> 32

const x2y3z = xyz(2)(3) // -> (Z) => 2 + 3 * Z
// even though Y * Z has priority in the curried function and should evaluate first, x2y3z can exist without Z)

x2y3z(4) // -> 14
x2y3z(10) // -> 32

v - Reverse

v(...A) returns an array filled with values of ...A in reversed order.

Examples:

v() // -> []
v(0) // -> [0]
v(1, 2, 3) // -> [3, 2, 1]
v('donkey') // -> ['donkey']
v(...'donkey').join('') // -> 'yeknod'

w - While

w(C, F, R) calls function F repeatedly as long as C() returns a truthy value. w() returns the return value of the last call to F. In each iteration, both calls to C and F are passed the return value of F() from the previous iteration. In the first iteration, this value is equal to R. If argument R is omitted, it defaults to 0. If F hasn't been called at all, the return value of w() is R.

Examples:

let A = 0, B = 0;
w(() => false, () => (console.log('foo'), 'bar')) // -> 0 (and nothing is logged)
w(() => true, () => console.log('donkey')) // -> throws RangeError: Maximum call stack size exceeded (this will print so many donkeys though...)
w(() => A < 5, () => (console.log(A), A++)) // -> 4 (and logs 0, 1, 2, 3, 4)
w(() => B < 5, () => (console.log(B), ++B)) // -> 5 (and logs 0, 1, 2, 3, 4)
w((C) => C < 5, (C) => (console.log(C), ++C)) // -> 5 (and logs 0, 1, 2, 3, 4)
w((V) => c(V), ([A, ...B]) => (console.log(A), B), [1, 2, 3]) // -> [] (and logs 1, 2, 3)

x - Extract

x(A, B) returns property A of B. B can be either an array, string or object and A must be a valid property name for the respective data type.

Examples:

x(0, [10, 20, 30]) // -> 10
x(3, 'donkey') // -> 'k'
x('foo', {foo: 'bar'}) // -> 'bar'

y - Create array

y(...A) return an array filled with values of ...A.

Examples:

y() // -> []
y(1) // -> [1]
y(0, 7, 42, 69, 88, 420, 1337, 9001) // -> [0, 7, 42, 69, 88, 420, 1337, 9001]
y(...'donkey') // -> ['d', 'o', 'n', 'k', 'e', 'y']

z - Zero

z(A) returns A converted to number. This only works for numbers, booleans, null and undefined. If argument A is omitted, it defaults to false, thus returning 0.

Examples:

z() // -> 0
z(true) // -> 1
z(-5) // -> -5
z(5) // -> 5
z(null) // -> 0

How does it work?

Each function provides very straightforward and simple logic. Through combinations of these functions, we can create more complex functions that can grow very powerful in only a few steps. This is the function dependency graph:

Dependecy graph

Functions with rectangular nodes use operators that could possibly be removed or refactored. Clone the project and open the graph locally in your browser for bonus interactivity!

How does it differ from JSFuck?

JSFuck works differently and has different goals. It basically tries to construct a JS code string from its 6 characters and then pass it to eval().

FP-JS doesn't need to use eval().

About

Minimal functional programming in JS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published