|
###! |
|
* funcoffee |
|
* Essential functional programming helpers |
|
* @author elclanrs |
|
* @license MIT |
|
### |
|
|
|
#- Essential |
|
|
|
_ = {} |
|
nop = -> |
|
id = (x) -> x |
|
|
|
builtin = (f) -> nop.call.bind f |
|
variadic = (as...) -> as |
|
apply = (f, as...) -> f [].concat.apply([], as)... |
|
notF = (f) -> (as...) -> not f as... |
|
|
|
curry = (f) -> do (recur = (as) -> |
|
next = (bs...) -> |
|
args = (as or [])[0..] |
|
if args.push.apply(args, bs) < f.length and bs.length |
|
return recur args |
|
f args... |
|
if f.length > 1 then next else f |
|
) |
|
|
|
partial = (f, as...) -> (bs...) -> |
|
args = as.concat bs |
|
i = args.length |
|
while i-- then if args[i] is _ |
|
args[i] = args.splice(-1)[0] |
|
f args... |
|
|
|
flip = curry (f, x, y) -> f [y, x]... |
|
flip3 = curry (f, x, y, z) -> f [z, y, x]... |
|
flipN = (f) -> (as...) -> f as.reverse()... |
|
|
|
compose = (fs...) -> fs.reduce (f, g) -> (as...) -> f g as... |
|
sequence = flipN compose |
|
|
|
#- Types |
|
|
|
isType = curry (t, x) -> Object::toString.call(x).slice(8,-1) is t |
|
instanceOf = curry (ctor, x) -> x instanceof ctor |
|
|
|
#- Logic |
|
|
|
isF = curry (x, y) -> x is y |
|
isntF = notF isF |
|
andF = curry (x, y) -> y && x |
|
orF = curry (x, y) -> y || x |
|
gte = curry (x, y) -> y >= x |
|
lte = curry (x, y) -> y <= x |
|
gt = notF lte |
|
lt = notF gte |
|
|
|
#- Math |
|
|
|
mod = curry (x, y) -> y % x is 0 |
|
even = mod 2 |
|
odd = notF even |
|
add = curry (x, y) -> x + y |
|
sub = curry (x, y) -> y - x |
|
mul = curry (x, y) -> x * y |
|
div = curry (x, y) -> y / x |
|
min = partial apply, Math.min |
|
max = partial apply, Math.max |
|
|
|
#- Arrays |
|
|
|
toArray = builtin Array::slice |
|
find = flip builtin Array::indexOf |
|
each = flip builtin Array::forEach |
|
map = flip builtin Array::map |
|
filter = flip builtin Array::filter |
|
fold = flip3 builtin Array::reduce |
|
foldr = flip3 builtin Array::reduceRight |
|
all = flip builtin Array::every |
|
any = flip builtin Array::some |
|
reverse = builtin Array::reverse |
|
join = flip builtin Array::join |
|
concat = builtin Array::concat |
|
sort = flip builtin Array::sort |
|
|
|
inArray = curry (x, xs) -> x in xs |
|
slice = curry (i, j, xs) -> if j? then xs[i...j] else xs[i..] |
|
|
|
first = ([x, xs...]) -> x |
|
last = ([xs..., x]) -> x |
|
rest = slice 1, null |
|
initial = slice 0, -1 |
|
take = slice 0 |
|
drop = partial slice, _, null, _ |
|
|
|
reject = curry (f, xs) -> filter notF(f), xs |
|
compact = filter Boolean |
|
|
|
unique = (xs) -> xs.filter (x, i) -> xs.indexOf(x) is i |
|
dups = (xs) -> xs.filter (x, i) -> xs.indexOf(x) isnt i |
|
|
|
zip = (xss...) -> xss[0].map (_, i) -> xss.map (xs) -> xs[i] |
|
zipWith = (f, xs...) -> map partial(apply, f), apply zip, xs |
|
|
|
flatten = (xs) -> |
|
xs.reduce (acc, x) -> |
|
if [].concat(x).some Array.isArray |
|
return acc.concat flatten x |
|
acc.concat x |
|
,[] |
|
|
|
flatMap = flip compose flatten, map |
|
union = compose unique, flatten, variadic |
|
intersection = compose unique, dups, flatten, variadic |
|
partition = curry (f, xs) -> [filter(f, xs), reject f, xs] |
|
difference = (xs, as...) -> reject inArray(unique(flatten as)), xs |
|
|
|
concatF = (fs...) -> (x) -> |
|
fs.reduce (acc, f, i) -> |
|
acc[i] = f x; acc |
|
,[] |
|
|
|
shuffle = (xs) -> |
|
for i in [xs.length-1..1] |
|
j = Math.random() * (i + 1) |0 |
|
[xs[i], xs[j]] = [xs[j], xs[i]] |
|
xs |
|
|
|
#- Strings |
|
|
|
split = flip builtin String::split |
|
match = flip builtin String::match |
|
replace = flip3 builtin String::replace |
|
search = flip builtin String::search |
|
substr = flip3 builtin String::substr |
|
trim = builtin String::trim |
|
toUpper = builtin String::toUpperCase |
|
toLower = builtin String::toLowerCase |
|
|
|
words = split ' ' |
|
unwords = join ' ' |
|
capitalize = replace toUpper, /^./ |
|
|
|
gmatch = curry (re, x) -> |
|
res = [] |
|
x.replace re, (as...) -> |
|
res.push.apply res, as[1...-2] |
|
res |
|
|
|
#- Objects |
|
|
|
toObject = (xs) -> |
|
xs.reduce (acc, x, i) -> |
|
acc[xs[i-1]] = x if i % 2 isnt 0 |
|
acc |
|
,{} |
|
|
|
forOwn = curry (acc, f, obj) -> |
|
i = 0 |
|
for own k, v of obj |
|
acc = f [acc, k, v, i++]... |
|
acc |
|
|
|
pluck = curry (x, xs) -> |
|
String(x).split('.').reduce (acc, x) -> |
|
if x of Object acc then acc[x] else undefined |
|
,xs |
|
|
|
pluckR = curry (x, xs) -> (xs while xs = pluck x, xs) |
|
pluckF = (x, as...) -> (obj) -> obj[x] as... |
|
|
|
pairs = forOwn [], (acc, k, v) -> acc.push [k, v]; acc |
|
|
|
zipObject = compose toObject, flatten, zip |
|
|
|
unzipObject = forOwn [[],[]], (acc, k, v, i) -> |
|
acc[0][i] = k; acc[1][i] = v |
|
acc |
|
|
|
keys = Object.keys |
|
values = compose pluck(1), unzipObject |
|
size = compose pluck('length'), keys |
|
has = compose notF(isType('Undefined')), pluck |
|
|
|
extend = partial forOwn, _, ((x, k, v) -> x[k] = v; x), _ |
|
merge = curry (x, y) -> extend extend({}, x), y |
|
|
|
#- Collections |
|
|
|
where = curry (obj, xs) -> |
|
xs.filter (x) -> |
|
Object.keys(obj).every (k) -> |
|
obj[k] is x[k] |
|
|
|
sortBy = curry (f, xs) -> |
|
xs.sort withF f, (x, y) -> |
|
if typeof x is 'number' then return x - y |
|
else if x > y then return 1 |
|
else if x < y then return -1 |
|
else 0 |
|
|
|
groupBy = curry (f, xs) -> |
|
xs.reduce (acc, x) -> |
|
fx = f x |
|
acc[fx] = (acc[fx] or []).concat x |
|
acc |
|
,{} |
|
|
|
countBy = sequence groupBy, forOwn (acc, k, v) -> |
|
acc[k] = v.length |
|
acc |
|
|
|
#- Functions |
|
|
|
withF = curry (f, g) -> compose partial(apply, g), map(f), variadic |
|
|
|
memo = (f) -> |
|
store = {} |
|
(x) -> |
|
return store[x] if x of store |
|
store[x] = f x |
|
|
|
#- Export |
|
|
|
module.exports = { |
|
_, nop, id, builtin, variadic, apply, notF, |
|
curry, partial, flip, flip3, flipN, compose, sequence, |
|
isType, instanceOf, isF, isntF, andF, orF, gt, lt, gte, lte, |
|
mod, even, odd, add, sub, mul, div, min, max, |
|
toArray, find, each, map, filter, fold, foldr, all, any, |
|
reverse, join, concat, sort, |
|
inArray, slice, first, last, rest, initial, take, drop, |
|
reject, compact, unique, dups, zip, zipWith, flatten, |
|
flatMap, union, intersection, partition, difference, concatF, shuffle, |
|
split, match, replace, search, substr, trim, |
|
toUpper, toLower, words, unwords, capitalize, gmatch, |
|
toObject, forOwn, pluck, pluckR, pluckF, |
|
pairs, zipObject, unzipObject, keys, values, size, has, |
|
extend, merge, |
|
where, sortBy, groupBy, countBy, |
|
withF, memo |
|
} |
|
|
|
module.exports.expose = partial extend, _, module.exports |