Explorar el Código

de{get,cow,comap}(x) => atPath(x).{get,put,map}

Herby Vojčík hace 6 años
padre
commit
f23b111c33
Se han modificado 2 ficheros con 58 adiciones y 84 borrados
  1. 37 49
      README.md
  2. 21 35
      index.js

+ 37 - 49
README.md

@@ -2,31 +2,35 @@
 
 
 Access and copy-on-modify JavaScript objects, including maps, using deep paths.
 Access and copy-on-modify JavaScript objects, including maps, using deep paths.
 
 
-### `deepGetOrNil(key, ...)`
-### `deget(key, ...)`
+### `atPath(key, ...)(key, ...)...`
 
 
-Creates an accessor function allowing
-to get specified key from any object.
+Returns set of accessing / modifying function for specified
+path of keys.
 
 
-Specify keys by passing a list of keys to `deepGetOrNil`.
+Specify keys by passing a list of keys to `atPath`.
 Key can be either:
 Key can be either:
  - number
  - number
  - `keyInMap(obj)`
  - `keyInMap(obj)`
  - array of Keys
  - array of Keys
  - anything else, which is `toString()`ed and dot-split.
  - anything else, which is `toString()`ed and dot-split.
 
 
+### `atPath(key, ...)(key, ...)... .get`
+
+An accessor function allowing
+to get specified key from any object.
+
 ```js
 ```js
-const name = deget("name");
+const name = atPath("name").get;
   
   
 name({name: "Tom"});
 name({name: "Tom"});
 // => "Tom"
 // => "Tom"
   
   
-const city = deget("address.city");
-const city2 = deget("address", "city");
+const city = atPath("address.city").get;
+const city2 = atPath("address", "city").get;
 // and other forms, like:
 // and other forms, like:
-// const city3 = deget(["address", "city"]);
-// const city4 = deget("address", [[], "city"]);
-// const city5 = deget([[], "address.city"]);
+// const city3 = atPath(["address", "city"]).get;
+// const city4 = atPath("address")([[], "city"]).get;
+// const city5 = atPath()([[], "address.city"])().get;
 // etc.
 // etc.
 const object = {address: {city: "New York"}};
 const object = {address: {city: "New York"}};
   
   
@@ -55,13 +59,12 @@ an object will be treated as an array.
 If you put a `keyInMap(obj)` in a list of keys to use,
 If you put a `keyInMap(obj)` in a list of keys to use,
 an object will be treated as a `Map`.
 an object will be treated as a `Map`.
 
 
-That way you can create eg. `const c = deget("person", 34, "name")`
+That way you can create eg. `const c = atPath("person", 34, "name").get`
 to access `obj.person[34].name` with `c(obj)`.
 to access `obj.person[34].name` with `c(obj)`.
 
 
-### `deepCopyOnWrite(key, ...)(val)`
-### `decow(key, ...)(val)`
+### `atPath(key, ...)(key, ...)... .put(val)`
 
 
-Creates a modifier function allowing
+A modifier function allowing
 to "set" specified key to any object
 to "set" specified key to any object
 in an immutable fashion, eg. creating a modified copy
 in an immutable fashion, eg. creating a modified copy
 when actual write happens.
 when actual write happens.
@@ -69,28 +72,21 @@ when actual write happens.
 If properties that are to be parents of a sub-value
 If properties that are to be parents of a sub-value
 are not present, they are created.
 are not present, they are created.
 
 
-Specify keys by passing a list of keys to `deepCopyOnWrite`.
-Key can be either:
- - number
- - `keyInMap(obj)`
- - array of Keys
- - anything else, which is `toString()`ed and dot-split.
-
 In case no change actually happens (same value is set which is
 In case no change actually happens (same value is set which is
 already present), returns the original object.
 already present), returns the original object.
 
 
 ```js
 ```js
-const setName = decow("name");
+const setName = atPath("name").put;
   
   
 setName("Jerry")({name: "Tom"});
 setName("Jerry")({name: "Tom"});
 // => {name: "Jerry"}
 // => {name: "Jerry"}
   
   
-const setCity = decow("address.city");
-const setCity2 = decow("address", "city");
+const setCity = atPath("address.city").put;
+const setCity2 = atPath("address", "city").put;
 // and other forms, like:
 // and other forms, like:
-// const setCity3 = decow(["address", "city"]);
-// const setCity4 = decow("address", [[], "city"]);
-// const setCity5 = decow([[], "address.city"]);
+// const setCity3 = atPath(["address", "city"]).put;
+// const setCity4 = atPath("address")([[], "city"]).put;
+// const setCity5 = atPath()([[], "address.city"])().put;
 // etc.
 // etc.
 const object = {address: {city: "New York"}};
 const object = {address: {city: "New York"}};
   
   
@@ -102,7 +98,7 @@ object;
 // => {address: {city: "New York"}}
 // => {address: {city: "New York"}}
 setCity("New York")(object) === object;
 setCity("New York")(object) === object;
 // => true
 // => true
-setCity("New York")(object) === object;
+setCity2("New York")(object) === object;
 // => true
 // => true
   
   
 const setCityLondon = setCity("London");
 const setCityLondon = setCity("London");
@@ -126,7 +122,7 @@ an object will be treated as an array (unlike the default string case,
 where it is treated as an object), so copy wil be created
 where it is treated as an object), so copy wil be created
 using `[...obj]`, not using `{...obj}`.
 using `[...obj]`, not using `{...obj}`.
 
 
-That way you can create eg. `const c = decow("person", 34, "name")`
+That way you can create eg. `const c = atPath("person", 34, "name").put`
 to "set" `obj.person[34].name` with `c(val)(obj)`.
 to "set" `obj.person[34].name` with `c(val)(obj)`.
 
 
 If you put a `keyInMap(key)` in a list of keys to use,
 If you put a `keyInMap(key)` in a list of keys to use,
@@ -134,10 +130,9 @@ an object will be treated as a `Map` (unlike the default string case,
 where it is treated as an object), so copy wil be created
 where it is treated as an object), so copy wil be created
 using `new Map(obj)`, not using `{...obj}`.
 using `new Map(obj)`, not using `{...obj}`.
 
 
-### `deepCopyOnMap(key, ...)(fn)`
-### `decomap(key, ...)(fn)`
+### `atPath(key, ...)(key, ...)... .map(fn)`
 
 
-Creates a modifier function allowing
+A modifier function allowing
 to "map" value at specified key
 to "map" value at specified key
 in an immutable fashion, eg. creating a modified copy
 in an immutable fashion, eg. creating a modified copy
 when actual modification happens.
 when actual modification happens.
@@ -147,28 +142,21 @@ are not present, they are _not_ created.
 In other words, if the key to modify does not exist,
 In other words, if the key to modify does not exist,
 no change happens.
 no change happens.
 
 
-Specify keys by passing a list of keys to `deepCopyOnMap`.
-Key can be either:
- - number
- - `keyInMap(obj)`
- - array of Keys
- - anything else, which is `toString()`ed and dot-split.
-
 In case no change actually happens (same value is set which is
 In case no change actually happens (same value is set which is
 already present), returns the original object.
 already present), returns the original object.
 
 
 ```js
 ```js
-const mapName = decomap("name");
+const mapName = atPath("name").map;
   
   
 mapName(x => x.toUpperCase())({name: "Tom"});
 mapName(x => x.toUpperCase())({name: "Tom"});
 // => {name: "TOM"}
 // => {name: "TOM"}
   
   
-const mapCity = decomap("address.city");
-const mapCity2 = decomap("address", "city");
+const mapCity = atPath("address.city").map;
+const mapCity2 = atPath("address", "city").map;
 // and other forms, like:
 // and other forms, like:
-// const mapCity3 = decomap(["address", "city"]);
-// const mapCity4 = decomap("address", [[], "city"]);
-// const mapCity5 = decomap([[], "address.city"]);
+// const mapCity3 = atPath(["address", "city"]).map;
+// const mapCity4 = atPath("address")([[], "city"]).map
+// const mapCity5 = atPath()([[], "address.city"])().map
 // etc.
 // etc.
 const object = {address: {city: "New York"}};
 const object = {address: {city: "New York"}};
   
   
@@ -204,8 +192,8 @@ an object will be treated as an array (unlike the default string case,
 where it is treated as an object), so copy wil be created
 where it is treated as an object), so copy wil be created
 using `[...obj]`, not using `{...obj}`.
 using `[...obj]`, not using `{...obj}`.
 
 
-That way you can create eg. `const c = decomap("person", 34, "name")`
-to "set" `obj.person[34].name` with `c(fn)(obj)`.
+That way you can create eg. `const c = atPath("person", 34, "name").map`
+to "map" `obj.person[34].name` with `c(fn)(obj)`.
 
 
 If you put a `keyInMap(key)` in a list of keys to use,
 If you put a `keyInMap(key)` in a list of keys to use,
 an object will be treated as a `Map` (unlike the default string case,
 an object will be treated as a `Map` (unlike the default string case,
@@ -220,7 +208,7 @@ Creates "`obj` as an index in a map".
 ### `cowWorkshop(keys, fn = x => x)(obj, [options])`
 ### `cowWorkshop(keys, fn = x => x)(obj, [options])`
 
 
 This is multipurpose enumerate-and-act function to manipulate objects
 This is multipurpose enumerate-and-act function to manipulate objects
-using `deget` and `decow`. The `options` argument can contain these additional fields:
+using `atPath`. The `options` argument can contain these additional fields:
   - `result` -- where to put elements (`obj` by default),
   - `result` -- where to put elements (`obj` by default),
   - `resultKeys` -- what keys to use to put into `result` (`keys` by default)
   - `resultKeys` -- what keys to use to put into `result` (`keys` by default)
   - `diff` -- where to put diffing elements (`undefined` by default)
   - `diff` -- where to put diffing elements (`undefined` by default)

+ 21 - 35
index.js

@@ -27,21 +27,14 @@ const copyWith = (key, value) =>
         isMapKey(key) ? copyMapWith(key.key, value) :
         isMapKey(key) ? copyMapWith(key.key, value) :
             copyObjectWith(key, value);
             copyObjectWith(key, value);
 
 
-const constructKeys = function (keyDescriptions) {
-    const keys = [];
-
-    function fillKeys (keyDescriptions) {
-        keyDescriptions.forEach(each => {
-            if (typeof each === 'number' || isMapKey(each)) keys.push(each);
-            else if (Array.isArray(each)) fillKeys(each);
-            else keys.push(...each.toString().split('.'));
-        });
-    }
-
-    fillKeys(keyDescriptions);
-
+function parseKeysInto (keyDescriptions, keys) {
+    keyDescriptions.forEach(each => {
+        if (typeof each === 'number' || isMapKey(each)) keys.push(each);
+        else if (Array.isArray(each)) fillKeys(each);
+        else keys.push(...each.toString().split('.'));
+    });
     return keys;
     return keys;
-};
+}
 
 
 const getKey = (x, key) => x == null ? undefined : isMapKey(key) ? x.get(key.key) : x[key];
 const getKey = (x, key) => x == null ? undefined : isMapKey(key) ? x.get(key.key) : x[key];
 
 
@@ -50,13 +43,6 @@ const getKeyWithCheck = (x, key) =>
         isMapKey(key) ? x.has(key.key) ? {value: x.get(key.key)} : null :
         isMapKey(key) ? x.has(key.key) ? {value: x.get(key.key)} : null :
             x.hasOwnProperty(key) ? {value: x[key]} : null;
             x.hasOwnProperty(key) ? {value: x[key]} : null;
 
 
-export const deepGetOrNil = (...keyDescriptions) => {
-    const keys = constructKeys(keyDescriptions);
-    return obj => keys.reduce(getKey, obj);
-};
-
-export const deget = deepGetOrNil;
-
 const chain = (list, stop, step) => (function worker (index) {
 const chain = (list, stop, step) => (function worker (index) {
     return index >= list.length ? stop : step(list[index], worker(index + 1));
     return index >= list.length ? stop : step(list[index], worker(index + 1));
 })(0);
 })(0);
@@ -66,25 +52,25 @@ const copyOnModification = (obj, key, value, modifierFn) => {
     return value === modified ? obj : copyWith(key, modified)(obj);
     return value === modified ? obj : copyWith(key, modified)(obj);
 };
 };
 
 
-export const deepCopyOnWrite = (...keyDescriptions) => {
-    const keys = constructKeys(keyDescriptions);
-    return val => chain(keys, () => val, (key, next) => x =>
-        copyOnModification(x, key, getKey(x, key), next)
-    );
-};
+const atPathBeginningWith = prefix => (...keyDescriptions) => {
+    const keys = parseKeysInto(keyDescriptions, [...prefix]);
+
+    return Object.assign(atPathBeginningWith(keys), {
+        get: obj => keys.reduce(getKey, obj),
 
 
-export const decow = deepCopyOnWrite;
+        put: val => chain(keys, () => val, (key, next) => x =>
+            copyOnModification(x, key, getKey(x, key), next)
+        ),
 
 
-export const deepCopyOnMap = (...keyDescriptions) => {
-    const keys = constructKeys(keyDescriptions);
-    return fn => chain(keys, fn, (key, next) => x => {
-        const valueWithCheck = getKeyWithCheck(x, key);
-        if (valueWithCheck == null) return x;
-        return copyOnModification(x, key, valueWithCheck.value, next);
+        map: fn => chain(keys, fn, (key, next) => x => {
+            const valueWithCheck = getKeyWithCheck(x, key);
+            if (valueWithCheck == null) return x;
+            return copyOnModification(x, key, valueWithCheck.value, next);
+        })
     });
     });
 };
 };
 
 
-export const decomap = deepCopyOnMap;
+export const atPath = atPathBeginningWith([]);
 
 
 export const cowWorkshop = (keys, fn = x => x) => (obj, {result = obj, resultKeys = keys, diff} = {}) => {
 export const cowWorkshop = (keys, fn = x => x) => (obj, {result = obj, resultKeys = keys, diff} = {}) => {
     keys.forEach((key, index) => {
     keys.forEach((key, index) => {