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.
51 lines • 4.59 kB
JavaScript
/*
* These functions are for mapping objects.
*
* To map arrays, functors or any other data type which has its own `map` method, use our `map`
* function, which dispatches to the prototype `map`, but capped.
*
* An object mapped using one of the `map` functions always results in a new object. The names
* `mapKeys`, `mapValues`, and `mapTuples` should make it clear how the mapping happens.
*
* An object mapped using one of the `remap` functions results in an array. The name `remap` is
* meant to make it clear that the shape changes. The `remap` functions follow the same form as the
* `map` functions.
*
* We map only over own, enumerable properties.
*
* Since arrays are objects in JS, `(re)mapKeys`, `(re)mapValues` and `(re)mapTuples` can also be
* used on arrays. The latter is useful when you start with an array and want to end up with an
* object. See `fromPairs` for an example of this.
*
* The WithFilter versions are exported so that map.js can create the mappings, but not further
* exported after that.
*
* Filters work on truthiness, and they take the mapped value as input.
*
* Map + filter is a convenience for avoiding `reduceObj`.
*
* It turns out in practice that the 'in' variants, which also loop through non-own properties, are
* not that useful for FP in JavaScript, so we don't provide them.
*
* 1) Inherited properties *tend* to be methods (though not always), so mapping, reducing etc. is
* not that useful.
* 2) Looping through non-own properties requires `for ... in`, which:
* a) doesn't include symbol properties: we consider symbol properties to be useful in FP, in
* addition to normal string properties. (`Ramda.keys` doesn't include them either);
* b) doesn't produce a guaranteed order.
*
* Nearly all of the built-in iteration functions in `Object` and `Reflect` only loop through own
* properties.
*
* @todo reduceAs etc.
*/import{propertyIsEnumerable,safeObject}from"./internal.mjs";const canEnum=(o,k)=>propertyIsEnumerable.call(o,k);export const remapKeys=f=>o=>{const ret=[];for(const k of Reflect.ownKeys(o))if(canEnum(o,k))ret.push(f(k));return ret;};export const _remapKeysWithFilter=p=>f=>o=>{const ret=[];for(const k of Reflect.ownKeys(o)){if(!canEnum(o,k))continue;const kk=f(k);if(p(kk))ret.push(f(k));}return ret;};export const remapValues=f=>o=>{const ret=[];for(const k of Reflect.ownKeys(o))if(canEnum(o,k))ret.push(f(o[k]));return ret;};export const _remapValuesWithFilter=p=>f=>o=>{const ret=[];for(const k of Reflect.ownKeys(o)){if(!canEnum(o,k))continue;const kk=f(o[k]);if(p(kk))ret.push(kk);}return ret;};export const remapTuples=f=>o=>{const ret=[];for(const k of Reflect.ownKeys(o))if(canEnum(o,k))ret.push(f(k,o[k]));return ret;};export const _remapTuplesWithFilter=p=>f=>o=>{const ret=[];for(const k of Reflect.ownKeys(o)){if(!canEnum(o,k))continue;const kkvv=f(k,o[k]);const[kk,vv]=kkvv;if(p(kk,vv))ret.push(kkvv);}return ret;};// --- note: in all functions which map keys or tuples, it's up to you to ensure that the keys don't
// clash.
// clashing keys will lead to unpredictable behavior between runtimes.
export const mapKeys=f=>o=>{const ret=safeObject();for(const k of Reflect.ownKeys(o))if(canEnum(o,k))ret[f(k)]=o[k];return ret;};export const _mapKeysWithFilter=p=>f=>o=>{const ret=safeObject();for(const k of Reflect.ownKeys(o)){if(!canEnum(o,k))continue;const kk=f(k);if(p(kk))ret[kk]=o[k];}return ret;};export const mapValues=f=>o=>{const ret=safeObject();for(const k of Reflect.ownKeys(o))if(canEnum(o,k))ret[k]=f(o[k]);return ret;};export const _mapValuesWithFilter=p=>f=>o=>{const ret=safeObject();for(const k of Reflect.ownKeys(o)){if(!canEnum(o,k))continue;const vv=f(o[k]);if(p(vv))ret[k]=vv;}return ret;};/* Map an array or an object to an object.
* note: filter is truthy, and works on the mapped value.
* note: it is up to the caller to ensure that the resulting keys don't clash.
*
* @experimental You can also use this to map an array to an object, since arrays are also objects.
* Be careful, because the index will be a String, not a Number.
* @future make separate function for arrays.
*/export const mapTuples=f=>o=>{const ret=safeObject();for(const k of Reflect.ownKeys(o)){if(!canEnum(o,k))continue;const[kk,vv]=f(k,o[k]);ret[kk]=vv;}return ret;};export const _mapTuplesWithFilter=p=>f=>o=>{const ret=safeObject();for(const k of Reflect.ownKeys(o)){if(!canEnum(o,k))continue;const[kk,vv]=f(k,o[k]);if(!p(kk,vv))continue;ret[kk]=vv;}return ret;};export const fromPairs=mapTuples((_,[k,v])=>[k,v]);export const toPairs=remapTuples((k,v)=>[k,v]);