Browse Source

Add deget, decow; deprecate cowValueModel.

Herby Vojčík 5 years ago
parent
commit
a9fbd6f229
2 changed files with 124 additions and 49 deletions
  1. 89 40
      README.md
  2. 35 9
      src/cow-value-model.js

+ 89 - 40
README.md

@@ -129,41 +129,30 @@ composeReducers(
 
 ## Redux helpers
 
-### `cowValueModel(key, ...)`
-
-Creates an overloaded function allowing
-to set or get specified key from any object.
+### `deepGetOrNil(key, ...)`
+### `deget(key, ...)`
 
-It gets when one arg passed, sets when two args passed.
-Setting `undefined` ends up as plain get
-(ES2015 default arguments semantics),
-so props are not created when not present
-if setting `undefined`; other values including `null` are ok
-and create nonpresent props.
+Creates an accessor function allowing
+to get specified key from any object.
 
-Specify keys by passing a list of keys to `cowValueModel`.
+Specify keys by passing a list of keys to `deepGetOrNil`.
 Key can be either:
  - number
  - array of Keys
  - anything else, which is `toString()`ed and dot-split.
 
-Set-usage (two-arg) returns a copy with specified (sub-)property changed;
-in case no change actually happens, returns the original object.
-
 ```js
-const name = cowValueModel("name");
+const name = deget("name");
   
 name({name: "Tom"});
 // => "Tom"
-name({name: "Tom"}, "Jerry");
-// => {name: "Jerry"}
   
-const city = cowValueModel("address.city");
-const city2 = cowValueModel("address", "city");
+const city = deget("address.city");
+const city2 = deget("address", "city");
 // and other forms, like:
-// const city3 = cowValueModel(["address", "city"]);
-// const city4 = cowValueModel("address", [[], "city"]);
-// const city5 = cowValueModel([[], "address.city"]);
+// const city3 = deget(["address", "city"]);
+// const city4 = deget("address", [[], "city"]);
+// const city5 = deget([[], "address.city"]);
 // etc.
 const object = {address: {city: "New York"}};
   
@@ -171,16 +160,6 @@ city(object);
 // => "New York"
 city2(object);
 // => "New York"
-city(object, "London");
-// => {address: {city: "London"}}
-city2(object, "London");
-// => {address: {city: "London"}}
-object;
-// => {address: {city: "New York"}}
-city(object, "New York") === object;
-// => true
-city2(object, "New York") === object;
-// => true
   
 city(undefined);
 // => undefined
@@ -194,18 +173,73 @@ city({address: {}});
 // => undefined
 city({address: {city: null}});
 // => null
+```
+
+If you put a number in a list of keys to use,
+an object will be treated as an array.
+
+That way you can create eg. `const c = deget("person", 34, "name")`
+to access `obj.person[34].name` with `c(obj)`.
+
+### `deepCopyOnWrite(key, ...)(val)`
+### `decow(key, ...)(val)`
+
+Creates a modifier function allowing
+to "set" specified key to any object
+in an immutable fashion, eg. creating a modified copy
+when actual write happens.
+
+If properties that are to be parents of a sub-value
+are not present, they are created.
+
+Specify keys by passing a list of keys to `deepCopyOnWrite`.
+Key can be either:
+ - number
+ - array of Keys
+ - anything else, which is `toString()`ed and dot-split.
+
+In case no change actually happens (same value is set which is
+already present), returns the original object.
+
+```js
+const setName = decow("name");
+  
+setName("Jerry")({name: "Tom"});
+// => {name: "Jerry"}
+  
+const setCity = decow("address.city");
+const setCity2 = decow("address", "city");
+// and other forms, like:
+// const setCity3 = decow(["address", "city"]);
+// const setCity4 = decow("address", [[], "city"]);
+// const setCity5 = decow([[], "address.city"]);
+// etc.
+const object = {address: {city: "New York"}};
+  
+setCity("London")(object);
+// => {address: {city: "London"}}
+setCity2("London")(object);
+// => {address: {city: "London"}}
+object;
+// => {address: {city: "New York"}}
+setCity("New York")(object) === object;
+// => true
+setCity("New York")(object) === object;
+// => true
   
-city(undefined, "London");
+const setCityLondon = setCity("London");
+  
+setCityLondon(undefined);
 // => {address: {city: "London"}}
-city(null, "London");
+setCityLondon(null);
 // => {address: {city: "London"}}
-city({}, "London");
+setCityLondon({});
 // => {address: {city: "London"}}
-city({address: null}, "London");
+setCityLondon({address: null});
 // => {address: {city: "London"}}
-city({address: {}}, "London");
+setCityLondon({address: {}});
 // => {address: {city: "London"}}
-city({address: {city: null}}, "London");
+setCityLondon({address: {city: null}});
 // => {address: {city: "London"}}
 ```
 
@@ -214,8 +248,23 @@ 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
 using `[...obj]`, not using `{...obj}`.
 
-That way you can create eg. `const c = cowValueObject("person", 34, "name")`
-to access `obj.person[34].name` with `c(obj)` / `c(obj, val)`.
+That way you can create eg. `const c = decow("person", 34, "name")`
+to "set" `obj.person[34].name` with `c(val)(obj)`.
+
+### `cowValueModel(key, ...)`
+
+Deprecated.
+
+Creates an overloaded function allowing
+to set or get specified key from any object.
+
+It gets when one arg passed (works as `deget(key, ...)`),
+sets when two args (obj, val) passed (works as `decow(key, ...)(val)(obj)`),
+with a caveat: setting `undefined` ends up as plain get
+(ES2015 default arguments semantics),
+so props are not created when not present
+if setting `undefined`; other values including `null` are ok
+and create nonpresent props.
 
 ### `typedAction(type, [fn])`
 

+ 35 - 9
src/cow-value-model.js

@@ -4,7 +4,7 @@ function copyWith (obj, key, value) {
     return result;
 }
 
-export const cowValueModel = (...keyDescriptions) => {
+const constructKeys = function (keyDescriptions) {
     const keys = [];
 
     function fillKeys (keyDescriptions) {
@@ -17,31 +17,57 @@ export const cowValueModel = (...keyDescriptions) => {
 
     fillKeys(keyDescriptions);
 
-    function setField (x, index, val) {
+    return keys;
+};
+
+const deepGet = (keys, obj) => keys.reduce((x, key) => x == null ? undefined : x[key], obj);
+
+const deepPut = (keys, obj, val) => {
+    function setVal (x, index) {
         if (index >= keys.length) return val;
         const key = keys[index],
             value = x == null ? undefined : x[key],
-            modified = setField(value, index + 1, val);
+            modified = setVal(value, index + 1);
         return value === modified ? x : copyWith(x, key, modified);
     }
 
+    return setVal(obj, 0);
+};
+
+export const deepGetOrNil = (...keyDescriptions) => {
+    const keys = constructKeys(keyDescriptions);
+    return obj => deepGet(keys, obj);
+};
+
+export const deget = deepGetOrNil;
+
+export const deepCopyOnWrite = (...keyDescriptions) => {
+    const keys = constructKeys(keyDescriptions);
+    return val => obj => deepPut(keys, obj, val);
+};
+
+export const decow = deepCopyOnWrite;
+
+export const cowValueModel = (...keyDescriptions) => {
+    const keys = constructKeys(keyDescriptions);
+
     const GET_SENTINEL = {};
 
     return (obj, val = GET_SENTINEL) =>
         val === GET_SENTINEL ?
-            keys.reduce((x, key) => x == null ? undefined : x[key], obj) :
-            setField(obj, 0, val);
+            deepGet(keys, obj) :
+            deepPut(keys, obj, val);
 };
 
 export const cowWorkshop = (keys, fn = x => x) => (obj, {result = obj, resultKeys = keys, diff} = {}) => {
     keys.forEach((key, index) => {
-        const value = fn(cowValueModel(key)(obj));
+        const value = fn(deget(key)(obj));
         if (typeof value === "undefined") return;
-        const modifier = cowValueModel(resultKeys[index]);
+        const modifier = decow(resultKeys[index])(value);
         const oldResult = result;
-        result = modifier(oldResult, value);
+        result = modifier(oldResult);
         if (result !== oldResult) {
-            diff = modifier(diff, value);
+            diff = modifier(diff);
         }
     });
     return {result, diff};