The script
//CHUTE: A JS pipeline helper to chain functions AND methods //By + Copyright Greg Abbott V 2024_1127 const chute = (()=>{//return keep_chaining?proxy:data let data//stores all data let key_is=null const error=(...x)=>{throw new Error(x)} const is_fn=x=>x instanceof Function const is_js_method=(o,k)=>is_fn(Object.getPrototypeOf(o)[k]) //^ is_js_method confirms if native method, ignores custom const chain_reducer=(data,f)=>{//chain(data,f1,f2) == f2(f1(data)) if(is_fn(f))return f(data) if(f==='log'){console.log(data);return data} else if(typeof f==='string'&&is_js_method(data,f))return data[f]() //^ Calls a js native method of data, if(expects args){normal error} else if(typeof f==='string'){error(`Data lacks "${f}" method`)} else {error('Not a function:',f)} return data } const call_method_of_data=(key,a)=>{ if(is_js_method(data,key)){data=data[key](...a)} else error(`Data lacks "${key}" method`) } const if_then=({cond,fn})=>{ const cond_is_fn=is_fn(cond) const good_cond=cond_is_fn||cond===false||cond===true if(!good_cond)error('give .if(arg1) bool OR test=data=>{…return bool}') if(!is_fn(fn))error('give .if(arg2) a Fn to send data to',fn) else if(cond_is_fn&&cond(data)===true)data=fn(data)//pass COND FN else if(cond===true)data=fn(data) //user gave true as cond } const proxy = new Proxy( function x(){}, { get(_,k){//_==FN x above //on 'method get' (i.e. on type '.anything') this activates key_is=k//save accessed key name, for any next call to use //e.g. `chain(data).x` triggers this block: sets `key_is='x'` //next call `chain(data).x(<-i.e. this call->)` uses key_is //could end chain on a get: return k=='value'?data:proxy return proxy }, //below allows unlimited alternating method and chain calls apply:(_,__, a)=>{//<-{_:proxy target,__:'this' Argument} let key = key_is//use once: chain().map(<-key=='map')(key==null) if(key_is)key_is=null//reset (null until next 'get') //Custom methods built into chain: .log .tap .if if(key=='log'){console.log(...a,data);}//Messages optional else if(key=='if'){if_then({cond:a[0],fn:a[1]})} //Tap: Send data to fn, chuck result, return data to chain else if(key=='tap'&&is_fn(a[0]))a[0](data) else if(key=='tap')error('give .tap() a fn to send data to') else if(key===null&&a.length===0)return data//blank call() DONE else if(key===null||key==='and')data=a.reduce(chain_reducer,data) else if(key)call_method_of_data(key,a/*method may take 0+ args*/) return proxy } //use://chain(data,fn1,fn2).K(this_call_uses_K)(fn3,fn4)(/*end*/) }) // (setup new chain & set data return (x,...fns)=>{ data=x//arg 1 == initial data //other args = initial chain: if(fns.length>0)data=fns.reduce(chain_reducer,data) return proxy } })()
Example use
//EXAMPLE //The Example let initial_data = [1, 2, 4] const result = chute(initial_data,/*any functions…*/push(8)) //swap between function calls and method calls as needed. //call any method native to the current data as normal: .map(double) //`.log` calls a built in method that takes 0+ arguments. .log('after data * 2:') //.log()//it logs any arguments and the data, then resumes. .filter(greater_than(4)) //`.and` calls a built in method that takes 1+ functions. //`.and(f1,f2)` sends data to f1, then f1's return to f2. .and(push(32),push(64)) //nameless calls work the same way as `.and`: (push(80,128),push(256)) //each sub-chain uses a reducer and mimics nested calls. //one call `(f1,f2)` == `f2(f1(data))` //many calls `(f1)(f2)` == `data=f1(data);data=f2(data)` ('reverse')//strings call methods that need no arguments. //this allows unbroken sequences: `(f1,'method',f2)` //'.tap' calls a built in method that takes a function. //'.tap(f1)' sends data to f1 but ignores the result. .log('pre tap') .tap(mutate)//.tap functions might mutate data directly. .log('post Tap') .reduce(add_up) //'.if' calls an inbuilt method that takes 2 arguments. .if( //a boolean //or a function that tests data and return a boolean. greater_than(64), //a function to send data to if the condition === true. halve ) .toString() .replace(/$/,'.') (append(' Thank you!')) ()//an empty call returns the final value //from this point, method calls directly act on the data. .toLowerCase() log({result}) //Dummy Helper Functions function push (...a){return l => (l.push(...a),l)} function log (x){console.log('outer_log',x);return x} function greater_than(n){ return x => x > n} function double(x){return x * 2} function halve(x){return x / 2} function mutate (data){ data.length=0 data.push(1452,1386,1483,1475) return 'chucks this returned value' } function add_up(a, b){return a + b} function append(x){return d => d+x}
By and copyright ©
Greg Abbott
2024