stick-js
Version:
Fast toolkit for functional programming in JS. Provides idioms for referentially transparent expressions, clear separation of mutable and immutable operations, object factories, function calls based on English grammar, and pipe & compose operators.
328 lines (321 loc) • 31.9 kB
JavaScript
/* As a general guideline we try to keep the functions here as fast as possible, even at the cost of code duplication.
*/import sprintfMod from'sprintf-js';const{sprintf}=sprintfMod;import{oStr,hasOwnProperty,safeObject}from"./internal.mjs";// --- @future curry these?
export const pipe=(a,b)=>b(a);export const composeRight=(a,b)=>(...args)=>b(a(...args));export const compose=(a,b)=>(...args)=>a(b(...args));// --- @experimental
export const composeAsMethodsRight=(b,a)=>a.compose(b);export const composeAsMethods=(a,b)=>a.compose(b);export const noop=()=>{};export const not=x=>!x;export const ok=x=>x!=null;export const notOk=x=>x==null;export const always=x=>_=>x;export const eq=x=>y=>x===y;export const ne=x=>y=>x!==y;export const gt=m=>n=>n>m;export const gte=m=>n=>n>=m;export const lt=m=>n=>n<m;export const lte=m=>n=>n<=m;/* Matches on exactly `true` or `false`
*/export const isTrue=eq(true);export const isFalse=eq(false);export const ifPredicate=p=>yes=>no=>x=>p(x)?yes(x):no(x);export const whenPredicate=p=>yes=>ifPredicate(p)(yes)(noop);export const ifPredicateV=p=>yes=>no=>x=>p(x)?yes:no;export const whenPredicateV=p=>yes=>ifPredicateV(p)(yes)(void 8);/* These are like `ifPredicate`/`whenPredicate`, but pass the result of the predicate test to the
* condition functions.
*/export const ifPredicateResults=p=>yes=>no=>x=>{const y=p(x);return y?yes(y):no(y);};export const whenPredicateResults=p=>yes=>ifPredicateResults(p)(yes)(noop);/* These are like `ifPredicate`/`whenPredicate`, but pass both `x` and the result of the predicate
* test to the condition functions.
*/export const ifPredicateWithResults=p=>yes=>no=>x=>{const y=p(x);return y?yes(x,y):no(x,y);};export const whenPredicateWithResults=p=>yes=>ifPredicateWithResults(p)(yes)(noop);/*
* An anaphoric if which branches on a constant condition. The piped argument is ignored when
* deciding the condition, but is passed to the branching functions.
*
* Usage:
* res | ifAlways (someConstantCondition) (
res => yes ...
res => no ...
)
*
* This is less lazy in its evaluation of the condition than the normal if/whenPredicate functions. To retain the laziness, use if/whenPredicate, and ignore the argument:
*
* res | ifPredicate (() => someConstantCondition) (...)
*
* Derivation:
* const isAlways = x => _ => x
* const ifAlways = x => isAlways (x) | ifPredicate
* const isAlways = always
* const ifAlways = x => always (x) | ifPredicate
* const ifAlways = always >> ifPredicate
*/export const ifAlways=composeRight(always,ifPredicate);export const whenAlways=composeRight(always,whenPredicate);/* Truthy / falsey */export const isYes=Boolean;export const isNo=not;// --- @future whenNil, ifNil
// ------ types.
export const getType=x=>oStr.call(x).slice(8,-1);export const isType=t=>x=>getType(x)===t;// --- Proxy counts as a function (it's how lodash does it).
export const isFunction=o=>{const type=getType(o);return false||type==='Function'||type==='GeneratorFunction'||type==='AsyncFunction'||type==='Proxy';};export const isArray=/*#__PURE__*/isType('Array');export const isObject=/*#__PURE__*/isType('Object');export const isNumber=/*#__PURE__*/isType('Number');export const isRegExp=/*#__PURE__*/isType('RegExp');export const isBoolean=/*#__PURE__*/isType('Boolean');export const isString=/*#__PURE__*/isType('String');export const isSymbol=/*#__PURE__*/isType('Symbol');export const isDate=/*#__PURE__*/isType('Date');export const isSet=/*#__PURE__*/isType('Set');export const isMap=/*#__PURE__*/isType('Map');export const tap=f=>o=>(f(o),o);export const dot=prop=>o=>o[prop]();export const dot1=prop=>val=>o=>o[prop](val);export const dot2=prop=>val1=>val2=>o=>o[prop](val1,val2);export const dot3=prop=>val1=>val2=>val3=>o=>o[prop](val1,val2,val3);export const dot4=prop=>val1=>val2=>val3=>val4=>o=>o[prop](val1,val2,val3,val4);export const dot5=prop=>val1=>val2=>val3=>val4=>val5=>o=>o[prop](val1,val2,val3,val4,val5);export const dotN=prop=>vs=>o=>o[prop](...vs);export const side=prop=>o=>(dot(prop)(o),o);export const side1=prop=>val1=>o=>(dot1(prop)(val1)(o),o);export const side2=prop=>val1=>val2=>o=>(dot2(prop)(val1)(val2)(o),o);export const side3=prop=>val1=>val2=>val3=>o=>(dot3(prop)(val1)(val2)(val3)(o),o);export const side4=prop=>val1=>val2=>val3=>val4=>o=>(dot4(prop)(val1)(val2)(val3)(val4)(o),o);export const side5=prop=>val1=>val2=>val3=>val4=>val5=>o=>(dot5(prop)(val1)(val2)(val3)(val4)(val5)(o),o);export const sideN=prop=>vs=>o=>(dotN(prop)(vs)(o),o);// --- @future reversed version of `has.
/* These all ignore symbol keys. */export const has=k=>o=>hasOwnProperty.call(o,k);export const hasIn=k=>o=>k in o;export const ifHas=yes=>no=>([o,k])=>has(k)(o)?yes(o[k],o,k):no(o,k);export const whenHas=yes=>ifHas(yes)(noop);export const ifHasIn=yes=>no=>([o,k])=>hasIn(k)(o)?yes(o[k],o,k):no(o,k);export const whenHasIn=yes=>ifHasIn(yes)(noop);export const bindLatePropTo=o=>prop=>(...args)=>o[prop](...args);export const bindLateProp=prop=>o=>(...args)=>o[prop](...args);export const bindPropTo=o=>prop=>o[prop].bind(o);export const bindProp=prop=>o=>o[prop].bind(o);export const bindTo=o=>f=>f.bind(o);export const bind=f=>o=>f.bind(o);// --- bindTry* returns `null` if o[prop] is not a function.
export const bindTryPropTo=o=>prop=>typeof o[prop]==='function'?bindPropTo(o)(prop):null;export const bindTryProp=prop=>o=>typeof o[prop]==='function'?bindPropTo(o)(prop):null;export const bindTryTo=o=>f=>typeof f==='function'?bindTo(o)(f):null;export const bindTry=f=>o=>typeof f==='function'?bindTo(o)(f):null;// --- @experimental
// --- @todo this is not that nice to use.
// --- trier is e.g. `bindTryPropTo`
export const ifBind=trier=>ifPredicateWithResults(passToN(trier));export const whenBind=trier=>yes=>ifBind(trier)(yes)(noop);/* Usage
* cond (
* [_ => 3 == 4, _ => 'twilight zone'],
* [_ => 3 == 5, _ => 'even stranger'],
* [T, _ => 'ok'],
* )
*
* or with a native idiom:
*
* cond (
* (_ => 3 == 4) | guard (_ => 'twilight zone'),
* (_ => 3 == 5) | guard (_ => 'even stranger'),
* otherwise | guard (_ => 'ok'),
* )
*
* `guardV` is a convenience for a guard which returns a simple expression, so guard (_ => 'twilight zone')
* could be replaced by guardV ('twilight zone')
*
* The advantage of the left side being functions is that they are late-evaluated, they stop as soon
* as one matches, and they can interact with a piped argument (see `condS` below.
*
* If you want simple, eager constant conditions on the left, just use a table :)
*
*/export const T=always(true);export const F=always(false);export const condPredicate=exec=>pred=>[pred,exec];export const condPredicateDiscard=exec=>pred=>[pred,(target,_result)=>exec(target)];/*
* Tests on truthiness, not strict equality.
* This is how `if` works, it's how `cond` works in Ramda, and it's trivial to convert truthy to
* strict.
*/export const cond=(...blocks)=>{for(const[test,exec]of blocks){const result=test();if(result)return exec(result);}};export const condS=blocks=>target=>{for(const[test,exec]of blocks){const result=test(target);if(result)return exec(target,result);}};export const add=m=>n=>m+n;export const multiply=m=>n=>m*n;export const divideBy=m=>n=>n/m;export const divideInto=m=>n=>m/n;export const subtract=m=>n=>n-m;export const subtractFrom=m=>n=>m-n;export const modulo=m=>n=>n%m;export const moduloWholePart=m=>n=>{const div=n/m;const flo=Math.floor(div);return div<0?1+flo:flo;};export const toThe=e=>b=>Math.pow(b,e);// ------ exceptions
// --- @todo consider adding tryCatchS:
// const tryCatchS = (good) => (bad) => (f) => (v) => {
// ... successVal = f (v)
// }
export const tryCatch=good=>bad=>f=>{// @todo simplify: try { return good (f ()) }
let successVal;try{successVal=f();}catch(e){return bad(e);}return good(successVal);};/* Despite the name, `die` simply throws an exception, which can of course be caught.
* (It shouldn't be too surprising to JS users that it doesn't really 'die', i.e. halt the runtime.)
*/export const exception=(...args)=>new Error(args.join(' '));export const raise=e=>{throw e;};export const die=composeRight(exception,raise);export const defaultTo=f=>x=>ok(x)?x:f();// ------ object stuff.
export const prop=p=>o=>o[p];export const propOf=o=>p=>o[p];export const path=xs=>o=>{let j=o;for(const i of xs)if(!ok(j))return j;else j=j[i];return j;};export const pathOf=o=>xs=>path(xs)(o);// --- @todo update and assoc convert array input to an object unfortunately (due to the merge step).
export const assoc=prop=>val=>o=>{const oo=mergeInM(o)(safeObject());oo[prop]=val;return oo;};export const assocM=prop=>val=>o=>(o[prop]=val,o);export const assocPath=xs=>x=>o=>assocPathM(xs)(x)(mergeInM(o)(safeObject()));export const assocPathM=xs=>x=>o=>{const reducer=(ptr,pat,el)=>{if(!ok(pat))return[ptr,el];const pp=ptr[pat];const ppp=isArray(pp)||isObject(pp)?pp:ptr[pat]=safeObject();return[ppp,el];};const[ptr,pat]=xs.reduce(([p,s],x)=>reducer(p,s,x),[o,null]);ptr[pat]=x;return o;};/* These always flatten the prototypes, like `assoc`. */export const updateM=prop=>f=>o=>(o[prop]=f(o[prop]),o);export const update=prop=>f=>o=>{const oo=merge(o)(safeObject());oo[prop]=f(o[prop]);return oo;};/* These are not separately unit tested because they route through `assocPath/M`, which are. */export const updatePathM=xs=>f=>o=>{const x=path(xs)(o);return assocPathM(xs)(f(x))(o);};export const updatePath=xs=>f=>o=>{const x=path(xs)(o);return assocPath(xs)(f(x))(o);};export const append=elem=>ary=>[...ary,elem];export const appendTo=ary=>elem=>[...ary,elem];export const appendToM=tgt=>src=>(tgt.push(src),tgt);export const appendM=src=>tgt=>(tgt.push(src),tgt);export const prependTo=ary=>elem=>[elem,...ary];export const prepend=elem=>ary=>[elem,...ary];export const prependM=src=>tgt=>(tgt.unshift(src),tgt);export const prependToM=tgt=>src=>(tgt.unshift(src),tgt);export const concatTo=tgt=>src=>tgt.concat(src);export const concat=src=>tgt=>tgt.concat(src);/* Note: these only work for arrays, not strings (strings are always immutable in JS)
*/export const concatToM=tgt=>src=>(tgt.push(...src),tgt);export const concatM=src=>tgt=>(tgt.push(...src),tgt);// --- @todo these seem to be much faster than Object.assign, check.
export const mergeToM=tgt=>src=>{for(const i of Reflect.ownKeys(src)){tgt[i]=src[i];}return tgt;};export const mergeM=src=>tgt=>{for(const i of Reflect.ownKeys(src))tgt[i]=src[i];return tgt;};export const mergeTo=tgt=>src=>{const a=mergeToM(safeObject())(tgt);return mergeToM(a)(src);};export const merge=src=>tgt=>{const a=mergeToM(safeObject())(tgt);return mergeToM(a)(src);};/* The 'in' variants ignore all symbol keys.
*/export const mergeInToM=tgt=>src=>{for(const i in src)tgt[i]=src[i];return tgt;};export const mergeInM=src=>tgt=>mergeInToM(tgt)(src);export const mergeInTo=tgt=>src=>{const a=mergeInToM(safeObject())(tgt);return mergeInToM(a)(src);};export const mergeIn=src=>tgt=>mergeInTo(tgt)(src);const getMergeX=pluck=>mergerSym=>ifPredicate(ok)(pluck)(_=>die(sprintf('No merge function for symbol "%s"')))(merges()[mergerSym]);// --- throw on failure.
const getMergeFunction=getMergeX(({f})=>f);const getMergeInfo=getMergeX(({to,mut,own})=>({to,mut,own}));export const mergeToMSym=Symbol('mergeToM');export const mergeToSym=Symbol('mergeTo');export const mergeMSym=Symbol('mergeM');export const mergeSym=Symbol('merge');export const mergeInToMSym=Symbol('mergeInToM');export const mergeInToSym=Symbol('mergeInTo');export const mergeInMSym=Symbol('mergeInM');export const mergeInSym=Symbol('mergeIn');// --- like R.mergeAll but also use prototype vals.
// --- to and from not applicable, also not curried or meant to be used piped.
export const mergeAllIn=xs=>xs.reduce((tgt,src)=>mergeInToM(tgt)(src),safeObject());const merges=_=>({[mergeToMSym]:{f:mergeToM,to:true,mut:true,own:true},[mergeToSym]:{f:mergeTo,to:true,mut:false,own:true},[mergeMSym]:{f:mergeM,to:false,mut:true,own:true},[mergeSym]:{f:merge,to:false,mut:false,own:true},[mergeInToMSym]:{f:mergeInToM,to:true,mut:true,own:false},[mergeInToSym]:{f:mergeInTo,to:true,mut:false,own:false},[mergeInMSym]:{f:mergeInM,to:false,mut:true,own:false},[mergeInSym]:{f:mergeIn,to:false,mut:false,own:false}});// --- tgt will be altered.
// only `own` needs to be passed: direction and mutability have already been decided.
//
// in the M case tgt is just the tgt;
// in the non-M case it has been prepared to be a new copy.
//
// `own` refers to both tgt & src -- not possible to mix and match.
// --- a performance hit is acceptable here.
const mergeXWith=collision=>own=>src=>tgt=>{const[_whenHas,_ifHas]=own?[whenHas,ifHas]:[whenHasIn,ifHasIn];const doKey=key=>_whenHas((v,o,k)=>_ifHas((v,o,k)=>tgt[key]=collision(src[key],tgt[key]))((o,k)=>tgt[key]=src[key])([tgt,key]))([src,key]);for(const key in src)doKey(key);// --- for non-own we ignore all symbol keys.
if(own)for(const key of Object.getOwnPropertySymbols(src))doKey(key);return tgt;};export const mergeWith=collision=>mergerSym=>{// --- fail early instead of continuing with curry (throws)
const merger=getMergeFunction(mergerSym);return a=>b=>{const{to,mut,own}=getMergeInfo(mergerSym);const[src,tgt]=to?[b,a]:[a,b];const tgtM=mut?tgt:to?merger(safeObject())(tgt):merger(tgt)(safeObject());return mergeXWith(collision)(own)(src)(tgtM);};};// --- like with 'with', mut and direction have already been arranged, and `tgt` will be mutated.
// --- tests `p` for truthiness.
const mergeXWhen=p=>own=>src=>tgt=>{const f=key=>{if(p(src[key],tgt[key]))tgt[key]=src[key];};if(own)for(const i of Reflect.ownKeys(src))f(i);else for(const i in src)f(i);return tgt;};// @todo mergeXWhen and mergeXWith should be one function => simpler
export const mergeWhen=p=>mergerSym=>{// --- fail early instead of continuing with curry (throws)
const merger=getMergeFunction(mergerSym);return a=>b=>{const{to,mut,own}=getMergeInfo(mergerSym);const[src,tgt]=to?[b,a]:[a,b];const tgtM=mut?tgt:to?merger(safeObject())(tgt):merger(tgt)(safeObject());return mergeXWhen(p)(own)(src)(tgtM);};};/* The following functions dispatch to the object's prototype method: `map`, `reduce`,
* `reduceRight`, `find`, `filter`, `reject`.
*
* 'Capped' below refers to the `f` or `p` functions, not to the prototype function (e.g. `map`
* itself).
*
* 'capped at 1' means the function will be called with exactly 1 argument.
*//* `f` is capped at 1.
*/export const map=f=>xs=>xs.map(x=>f(x));export const each=f=>xs=>xs.forEach(x=>f(x));/* `f` is capped at 2.
* dispatches to `xs.reduce`
*/export const reduce=f=>acc=>xs=>xs.reduce((acc,x)=>f(acc,x),acc);/* `f` is capped at 2.
*
* Does not dispatch to `xs.reduceRight`. Also, the order of `acc` and `f` within the reducer is reversed with respect to
* `xs.reduceRight`, but is the familiar order from Haskell.
*
*/export const reduceRight=f=>acc=>xs=>{let acco=acc;const l=xs.length;if(l===0)return acco;for(let i=l-1;i>=0;i-=1)acco=f(xs[i],acco);return acco;};/* Like `reduceRight`, but assumes `f` is curried.
*
* This allows you to write `f` in a more point-free way in certain cases, by omitting the accumulator.
*
* For example, a function which multiplies each element by 2 and reverses the list:
*
* const reverseDouble = reduceRight (
* (x, acc) => {
* const clone = [... acc]
* clone.push (x * 2)
* return clone
* },
* [],
* )
*
* can be written as
*
* const reverseDouble = reduceRight (
* (x, acc) => acc | append (x * 2),
* [],
* )
*
* Then, using `reduceRightC`:
*
* const reverseDouble = reduceRightC (
* x => acc => acc | append (x * 2),
* [],
* )
*
* And finally:
*
* const reverseDouble = reduceRightC (
* x => append (x * 2),
* [],
* )
*
* And if you want to go further:
*
* const reverseDouble = reduceRightC (
* multiply (2) >> append,
* [],
* )
*
* Note: does not work with addIndex/addCollection (will need separate 'C' versions of those).
*/export const reduceRightC=f=>accInit=>xs=>{let acc=accInit;const l=xs.length;if(l===0)return acc;for(let i=l-1;i>=0;i-=1)acc=f(xs[i])(acc);return acc;};/* `p` is capped at 1.
*
* Note that it's not possible to search for `undefined` in a list using this function. If you
* really need this, consider `findIndex` or `contains`/`containsV`.
*/export const find=p=>xs=>xs.find(x=>p(x));export const findIndex=p=>xs=>{let i=0;for(const x of xs){if(p(x))return i;i++;}return undefined;};export const findWithIndex=p=>xs=>{let i=0;for(const x of xs){if(p(x))return[x,i];i++;}return[undefined,undefined];};/* `f` is capped at 1.
*/export const filter=f=>xs=>xs.filter(x=>f(x));export const reject=f=>xs=>xs.filter(x=>!f(x));export const containsV=v=>xs=>{for(const x of xs)if(x===v)return true;return false;};export const contains=p=>xs=>{for(const x of xs)if(p(x))return true;return false;};/* @experimental
*
* Not clear this is useful. abortVal should be a predicate instead of a val, and then, why not just
* allow the caller to add a condition to `f`?
*/export const reduceAbort=f=>accInit=>abortVal=>xs=>{let acc=accInit;for(const x of xs){let g=f(acc,x);if(g===abortVal)return abortVal;acc=g;}return acc;};// --- dropping too many returns an empty list.
export const drop=x=>xs=>xs.slice(x);// --- taking too many returns the entire list.
export const take=x=>xs=>xs.slice(0,x);// --- returns obj. Ignores symbol keys of `o`.
export const eachObj=f=>o=>{for(const k in o)if(hasOwnProperty.call(o,k))f(o[k],k);return o;};// --- returns obj
export const eachObjIn=f=>o=>{for(const k in o)f(o[k],k);return o;};/* `addIndex` and `addCollection` work on lists and objects.
*/export const addIndex=orig=>f=>{let idx=-1;const g=(...args)=>f(...args,++idx);return orig(g);};export const addCollection=orig=>f=>coll=>{const g=(...args)=>f(...args,coll);return orig(g)(coll);};// --- successive forms can use the same function but just need to be recurried with different
// arity.
export const addIndex2=addIndex;/*
const addIndexC = (orig) => (f) => {
let idx = -1
// --- assumes args is non-empty (i.e., the function being mapped/reduced/etc. can't be called
// without arguments.
const g = (...args) => {
const [x, ...ys] = args
let o = f (x)
for (const y in ys)
o = o (y)
return o (++idx)
}
return orig (g)
}
*/export const addCollection2=orig=>f=>x=>coll=>{const g=(...args)=>f(...args,coll);return orig(g)(x)(coll);};/* Ignores symbol keys of `o`. */export const reduceObj=f=>acc=>o=>{let curAcc=acc;for(const k in o)if(hasOwnProperty.call(o,k))curAcc=f(curAcc,[k,o[k]]);return curAcc;};export const reduceObjIn=f=>acc=>o=>{let curAcc=acc;for(const k in o)curAcc=f(curAcc,[k,o[k]]);return curAcc;};/* --- @experimental: need more intuitive interface.
* --- e.g. 3 | ampersandN ([inc, double, odd]) = [4, 6, true]
* --- could alias `pam`:
* const pam = ampersandN
* 3 | pam ([inc, double, odd])
*/export const ampersandN=fs=>x=>{const mapper=f=>f(x);return map(mapper)(fs);};/* @todo
asteriskNN: fs => xs
asterisk2N: f => g => xs
asterisk2: f => g => a => b
asteriskMapNN: fs => xs
asteriskMap2N: f => g => xs
asteriskMap2: f => g => a => b
asteriskAppNN: xs => fs
asteriskApp2N: a => b => fs
asteriskApp2: a => b => f => g
anvilNN
asterisk = anvilNN
const arrowSnd = f => timesV (2) >> asteriskN ([id, f])
*/// --- @experimental
// --- asterisk is like a dot product of vectors: it takes a list of functions and a list of values
// and runs the functions corresponding to position.
// --- @todo make more intuitive.
export const asteriskN=fs=>xs=>{const ret=[];let i=-1;for(const f of fs){const x=xs[++i];ret.push(f(x));}return ret;};// --- @todo: asteriskN, for when given an array of functions and a corresponding array of callees.
export const asterisk1=f=>a=>[f(a)];export const asterisk2=f=>g=>a=>b=>[f(a),g(b)];export const asterisk3=f=>g=>h=>a=>b=>c=>[f(a),g(b),h(c)];export const asterisk4=f=>g=>h=>i=>a=>b=>c=>d=>[f(a),g(b),h(c),i(d)];export const asterisk5=f=>g=>h=>i=>j=>a=>b=>c=>d=>e=>[f(a),g(b),h(c),i(d),j(e)];// --------- lets / let / let...
export const letNV=xs=>f=>f.apply(null,xs);/*
* lets = let* from racket
* letN = let* + array
* letS = let* + stick (S implies N)
* lets1, lets2, etc.: wrapped by lets, but can be called directly too.
* letNV = like letV, with array
* letV = let with values instead of functions
*/// --- last arg must be a function.
// --- 1 arg is possible but trivial.
export const letV=(...xs)=>{const f=xs.pop();return letNV(xs)(f);};/*
* For example, our `defaultTo` takes a function:
* null | defaultTo (_ -> 'bad news')
* For simple values, defaultToV can be more convenient:
* null | defaultToV ('bad news')
*/// --- trivial form.
export const lets1=f=>invoke(f);// --- @experimental lets2 - lets6 can be called directly as an optimisation (there is no pushing to
// an array or spreading values)
export const lets2=(f1,f2)=>{const n1=f1();return f2(n1);};export const lets3=(f1,f2,f3)=>{const n1=f1();const n2=f2(n1);return f3(n1,n2);};export const lets4=(f1,f2,f3,f4)=>{const n1=f1();const n2=f2(n1);const n3=f3(n1,n2);return f4(n1,n2,n3);};export const lets5=(f1,f2,f3,f4,f5)=>{const n1=f1();const n2=f2(n1);const n3=f3(n1,n2);const n4=f4(n1,n2,n3);return f5(n1,n2,n3,n4);};export const lets6=(f1,f2,f3,f4,f5,f6)=>{const n1=f1();const n2=f2(n1);const n3=f3(n1,n2);const n4=f4(n1,n2,n3);const n5=f5(n1,n2,n3,n4);return f6(n1,n2,n3,n4,n5);};export const letN=xs=>lets(...xs);export const lets=(...fs)=>{const acc=[];let last;for(const f of fs){const ret=f(...acc);acc.push(ret);last=ret;}return last;};export const letS=specAry=>tgt=>lets(_=>tgt,...specAry);// ------ call/provide
export const callOn=o=>f=>f.call(o);export const callOn1=o=>val1=>f=>f.call(o,val1);export const callOn2=o=>val1=>val2=>f=>f.call(o,val1,val2);export const callOn3=o=>val1=>val2=>val3=>f=>f.call(o,val1,val2,val3);export const callOn4=o=>val1=>val2=>val3=>val4=>f=>f.call(o,val1,val2,val3,val4);export const callOn5=o=>val1=>val2=>val3=>val4=>val5=>f=>f.call(o,val1,val2,val3,val4,val5);export const callOnN=o=>vs=>f=>f.apply(o,vs);export const provideTo=f=>o=>f.call(o);export const provideTo1=f=>val=>o=>f.call(o,val);export const provideTo2=f=>val1=>val2=>o=>f.call(o,val1,val2);export const provideTo3=f=>val1=>val2=>val3=>o=>f.call(o,val1,val2,val3);export const provideTo4=f=>val1=>val2=>val3=>val4=>o=>f.call(o,val1,val2,val3,val4);export const provideTo5=f=>val1=>val2=>val3=>val4=>val5=>o=>f.call(o,val1,val2,val3,val4,val5);export const provideToN=f=>vs=>o=>f.apply(o,vs);export const invoke=f=>f();export const applyTo1=val1=>f=>f(val1);export const applyTo2=val1=>val2=>f=>f(val1,val2);export const applyTo3=val1=>val2=>val3=>f=>f(val1,val2,val3);export const applyTo4=val1=>val2=>val3=>val4=>f=>f(val1,val2,val3,val4);export const applyTo5=val1=>val2=>val3=>val4=>val5=>f=>f(val1,val2,val3,val4,val5);export const applyToN=vs=>f=>f.apply(null,vs);export const passTo=f=>val=>f(val);export const passToN=f=>vs=>f.apply(null,vs);export const spreadTo=passToN;// ------ join, split etc.
export const join=dot1('join');export const split=dot1('split');export const flip=f=>a=>b=>f(b)(a);export const flip3=f=>a=>b=>c=>f(b)(a)(c);export const flip4=f=>a=>b=>c=>d=>f(b)(a)(c)(d);export const flip5=f=>a=>b=>c=>d=>e=>f(b)(a)(c)(d)(e);// ------ sprintf
export const sprintf1=str=>a=>sprintf(str,a);export const sprintfN=str=>xs=>sprintf.apply(null,[str,...xs]);// ------ repeat, times
export const repeatV=x=>n=>{const ret=[];for(let i=0;i<n;i++)ret.push(x);return ret;};export const repeatF=f=>n=>{const ret=[];for(let i=0;i<n;i++)ret.push(f(i));return ret;};export const repeatSide=f=>n=>{for(let i=0;i<n;i++)f(i);};export const timesV=n=>x=>repeatV(x)(n);export const timesF=n=>f=>repeatF(f)(n);export const timesSide=n=>f=>repeatSide(f)(n);// ------ replace / match
export const ifReplace=yes=>no=>re=>replArg=>target=>{let success=0;const repl=typeof replArg==='function'?(...args)=>(++success,replArg(...args)):_=>(++success,replArg);const out=target.replace(re,repl);return success?yes(out,success):no(target);};// --- by should be negative to count down.
export const rangeFromBy=by=>from=>to=>from<to?rangeFromByAsc(by)(from)(to):from>to?rangeFromByDesc(by)(from)(to):[];// --- no corresponding to version.
export const rangeFromByAsc=by=>from=>to=>{const ret=[];for(let i=from;i<to;i+=by)ret.push(i);return ret;};// --- no corresponding to version.
export const rangeFromByDesc=by=>from=>to=>{const ret=[];for(let i=from;i>to;i+=by)ret.push(i);return ret;};export const rangeToBy=by=>to=>from=>from<to?rangeFromByAsc(by)(from)(to):from>to?rangeFromByDesc(by)(from)(to):[];// --------- regex.
// --- these deviate somewhat from the naming conventions: we're assuming you generally want to pipe
// the target to the match functions.
const removeSpaces=/*#__PURE__*/dot2('replace')(/\s+/g)('');// --- input: regex.
export const xRegExp=re=>new RegExp(removeSpaces(re.source),re.flags);// @todo curry
// --- beware, overwrites any flags that the re already had.
export const xRegExpFlags=(re,flags)=>new RegExp(removeSpaces(re.source),flags);// @todo curry, check default flags.
// --- input: string.
export const xRegExpStr=(reStr,flags='')=>lets(_=>removeSpaces(reStr),_=>flags,(x,y)=>neu2(RegExp)(x)(y));// --- not every function (currently) has a matching 'replace' version.
// @todo make xMatch incur only a compile-time cost.
export const neu1=x=>val1=>new x(val1);export const neu2=x=>val1=>val2=>new x(val1,val2);export const neu3=x=>val1=>val2=>val3=>new x(val1,val2,val3);export const neu4=x=>val1=>val2=>val3=>val4=>new x(val1,val2,val3,val4);export const neu5=x=>val1=>val2=>val3=>val4=>val5=>new x(val1,val2,val3,val4,val5);export const neuN=x=>vs=>new x(...vs);export const match=re=>target=>re.exec(target);export const xMatchGlobal=re=>mapper=>target=>{let out=[];const reGlobal=xRegExpFlags(re,'g');let m;while(m=reGlobal.exec(target))appendToM(out)(mapper(...m));return out;};export const xMatch=re=>target=>xRegExp(re).exec(target);export const xMatchStr=reStr=>target=>xMatch(new RegExp(reStr))(target);export const xMatchStrFlags=reStr=>flags=>target=>xMatch(new RegExp(reStr,flags))(target);export const xReplace=re=>repl=>target=>target.replace(xRegExp(re),repl);export const xReplaceStr=reStr=>repl=>target=>target.replace(xRegExpStr(reStr),repl);export const xReplaceStrFlags=reStr=>flags=>repl=>target=>target.replace(xRegExpStr(reStr,flags),repl);export const ifXReplace=re=>repl=>yes=>no=>target=>ifReplace(yes)(no)(xRegExp(re))(repl)(target);export const ifXReplaceStr=reStr=>repl=>yes=>no=>target=>ifReplace(yes)(no)(xRegExpStr(reStr))(repl)(target);export const ifXReplaceStrFlags=reStr=>flags=>repl=>yes=>no=>target=>ifReplace(yes)(no)(xRegExpStr(reStr,flags))(repl)(target);/* The first arg can be either a function or an object. If it's a function, then it will be called
* on each `create`. The object form avoids this extra call, but beware that if you use it then the
* values should all be primitive types, or undefined or null. If they're reference types they will
* be shared among all object instances and that's probably not what you want. You can initialise
* the values during `init`. The optional object passed to `create` is assumed to only contain
* string keys (symbol keys are ignored).
*/export const factoryProps=propsArg=>factory=>{const orig=(...args)=>factory.create(...args);// @todo don't clone factory?
return{...factory,create(args){const props=typeof propsArg==='function'?propsArg():propsArg;const o=orig(props);const[src,tgt]=[args,o];for(const i in args)if(hasOwnProperty.call(src,i)&&ok(src[i]))tgt[i]=src[i];return tgt;}};};export const factoryInit=init=>proto=>({proto,create:props=>{const o=Object.create(proto);init(o,props);return o;}});// --- alters the prototype by merging in the mixins.
// --- if a key exists in the proto and its value is not nil, then it is not overwritten.
// --- the result is as if you had merged the proto into the mixin, keeping the proto's own
// prototype chain intact, then called that result the proto; except that the proto is altered in
// place of course because of the M.
export const mixinPreM=mixin=>proto=>{const chooseTgtWhenOk=(src,tgt)=>ok(tgt)?tgt:src;const mergeInToChooseTgtWhenOkM=mergeWith(chooseTgtWhenOk)(mergeInToMSym);return mergeInToChooseTgtWhenOkM(proto)(mixin);};// --- alters the prototype by merging in the mixins.
// --- if a key exists in the proto then it is overwritten, unless the corresponding value from the
// mixin is nil.
export const mixinM=mixin=>proto=>{const srcOk=(src,_)=>ok(src);const mergeInToWhenSrcOkM=mergeWhen(srcOk)(mergeInToMSym);return mergeInToWhenSrcOkM(proto)(mixin);};export const mixinPreNM=ms=>proto=>ms.reduce((protoAcc,mixin)=>mixinPreM(mixin)(protoAcc),proto);export const mixinNM=ms=>proto=>ms.reduce((protoAcc,mixin)=>mixinM(mixin)(protoAcc),proto);// ------ all/any.
// --- no point in using an N marker, because non-n is only possible once you know the predicate.
// ------ these all return truthy values instead of strict booleans; rationale: it's trivial to
// convert to a boolean afterwards using for example `| Boolean` or `>> Boolean`, but not the other
// way around.
// --- returns the return value of the last function, if they all return truthy, otherwise `false`.
export const againstAll=fs=>x=>{let y=false;for(const f of fs){y=f(x);if(!y)return false;}return y;};// --- returns the return value of the first function which returns truthy, otherwise `false`.
export const againstAny=fs=>x=>{let y;for(const f of fs){y=f(x);if(y)return y;}return false;};// --- returns the return value of the last function if they are all truthy, otherwise `false`.
export const allAgainst=f=>xs=>{let y=false;for(const x of xs){y=f(x);if(!y)return false;}return y;};// --- returns the return value of the first function which returns truthy, otherwise `false`.
export const anyAgainst=f=>xs=>{let y;for(const x of xs){y=f(x);if(y)return y;}return false;};export const againstBoth=f=>g=>x=>f(x)&&g(x);export const againstEither=f=>g=>x=>f(x)||g(x);// @todo equiv: like R.equals
// @todo pop, popFrom, popM, popFromM, also shift.
/*
* Usage:
* const x = { some: 2, vals: 10, }
* x | deconstruct ({ some, vals, }) => {
* ...
* }
* x | deconstruct ({ some, vals, }, orig) => {
* ...
* }
*/export const deconstruct=f=>o=>f(o,o);/*
* @experimental: need better name.
*
* Useful for when you want to deconstruct some arguments, but also keep a reference to the
* original:
*
* Usage:
*
* const x = { some: 3, vals: 4, }
* x | deconstruct2 (({ some, vals, }) => merge ({
* new: some + 2,
* extra: vals + 3,
* }) // => { some: 3, vals: 4, new: 5, extra: 7, }
*
* Or say you have a redux reducer function like:
*
* [CONSTANT]: (actionPayload) => (state) => newState
*
* where the newState depends on a value in the state:
*
* [INCREASE_COUNTER]: ({ data: increaseBy, }) => deconstruct2 (
* ({ counter, }) => state => state | merge ({
* counter: counter + increaseBy,
* }),
* ),
*
* or:
*
* [INCREASE_COUNTER]: ({ data: increaseBy, }) => deconstruct2 (
* ({ counter, }) => merge ({
* counter: counter + increaseBy,
* }),
* ),
*/export const deconstruct2=f=>o=>f(o)(o);/*
* Deconstruct an array of values:
*
* Usage, e.g. in a React component method:
* componentDidMount = this | deconstruct (
* ({ props, state, }) => [props, state] | deconstructN (
* ({ prop1, prop2, }, { state1, state2, }) => ...
* )
* )
*/export const deconstructN=f=>xs=>f(...xs);export const and=y=>x=>x&&y;export const or=y=>x=>x||y;/* export const andNot = not >> and
* export const orNot = not >> or
*/export const andNot=y=>x=>x&&!y;export const orNot=y=>x=>x||!y;