Repository: sanctuary-js/sanctuary
Pull request #435 (2017-08-22 00:33:51)

strmap: add S.insert and S.remove
As discussed in issue #434.

gabejohnson: We may want to consider adding `delete` and `update` as well while we're at it.
davidchambers: Lovely pull request, @samueller! I see that you took care to follow the project's conventions. :tada:

> We may want to consider adding `delete` and `update` as well while we're at it.

I agree. I'd been thinking [`delete`][1] would be a natural inclusion in this pull request. [`update`][2] is new to me, but it's closely related to the other two functions.

Scott, please add `delete` to this pull request. You're welcome to add `update` too, but I don't mind leaving that for another pull request.


[1]: https://hackage.haskell.org/package/containers-0.5.10.2/docs/Data-Map-Internal.html#v:delete
[2]: https://hackage.haskell.org/package/containers-0.5.10.2/docs/Data-Map-Internal.html#v:update

gabejohnson: @davidchambers I'm rethinking `update`. It should probably go in a separate PR with `alter`.

They both rely on `Maybe` whereas `insert` and `delete` do not.

`alter` can be implemented in terms of `insert` and `delete` and `update` in terms of `alter`.
davidchambers: > It should probably go in a separate PR with `alter`.

Okay. `insert` and `delete` should still be inserted into the project atomically. :)

samueller: @davidchambers, @gabejohnson. So I'll add `delete` to this PR and nothing else, correct?
davidchambers: > So I'll add `delete` to this PR and nothing else, correct?

:thumbsup:

samueller: I created the `S.delete` function, but ESLint is reporting an error when I run `make test lint`:
```
/Users/scott/Documents/code/sanctuary/index.js
  3754:5  error  .delete is a syntax error  dot-notation

✖ 1 problem (1 error, 0 warnings)

make: *** [lint] Error 1
```
I'm guessing this is a problem because `delete` is a keyword in JavaScript. Do we modify the ESLint configuration or change the name of this function?
gabejohnson: We have to change it. Calling it `delete` will mean that no one will be able to use it by that name unqualified.

We could call it `remove`, `deleteFrom`, `deleteEntry`, or what have you.

I apologize. This should have occurred to me before I was suggesting it.
samueller: I like `remove`.
samueller: Don't know if there was a notification, but I pushed changes yesterday
davidchambers: > Don't know if there was a notification, but I pushed changes yesterday

There are notifications for regular pushes but not for `--force` pushes.

I'll have a look at the changes now. :)

davidchambers: Would you mind having another look at this pull request, @gabejohnson? :eyes:


Patch:
diff --git a/index.js b/index.js
index ee0aa78..d36dcca 100644
--- a/index.js
+++ b/index.js
@@ -3757,6 +3757,51 @@
   }
   S.singleton = def('singleton', {}, [$.String, a, $.StrMap(a)], singleton);
 
+  //# insert :: String -> a -> StrMap a -> StrMap a
+  //.
+  //. Takes a string, a value of any type, and a string map, and returns a
+  //. string map comprising all the entries of the given string map plus the
+  //. entry specified by the first two arguments (which takes precedence).
+  //.
+  //. Equivalent to Haskell's `insert` function. Similar to Clojure's `assoc`
+  //. function.
+  //.
+  //. ```javascript
+  //. > S.insert('c', 3, {a: 1, b: 2})
+  //. {a: 1, b: 2, c: 3}
+  //.
+  //. > S.insert('a', 4, {a: 1, b: 2})
+  //. {a: 4, b: 2}
+  //. ```
+  function insert(key, val, strMap) {
+    return Z.concat(strMap, singleton(key, val));
+  }
+  S.insert =
+  def('insert', {}, [$.String, a, $.StrMap(a), $.StrMap(a)], insert);
+
+  //# remove :: String -> StrMap a -> StrMap a
+  //.
+  //. Takes a string and a string map, and returns a string map comprising all
+  //. the entries of the given string map except the one whose key matches the
+  //. given string (if such a key exists).
+  //.
+  //. Equivalent to Haskell's `delete` function. Similar to Clojure's `dissoc`
+  //. function.
+  //.
+  //. ```javascript
+  //. > S.remove('c', {a: 1, b: 2, c: 3})
+  //. {a: 1, b: 2}
+  //.
+  //. > S.remove('c', {})
+  //. {}
+  //. ```
+  function remove(key, strMap) {
+    var result = Z.concat(strMap, {});
+    delete result[key];
+    return result;
+  }
+  S.remove = def('remove', {}, [$.String, $.StrMap(a), $.StrMap(a)], remove);
+
   //# keys :: StrMap a -> Array String
   //.
   //. Returns the keys of the given string map, in arbitrary order.


Test patch:
diff --git a/test/insert.js b/test/insert.js
new file mode 100644
index 0000000..c920cc4
--- /dev/null
+++ b/test/insert.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var jsc = require('jsverify');
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('insert', function() {
+
+  eq(typeof S.insert, 'function');
+  eq(S.insert.length, 3);
+  eq(S.insert.toString(), 'insert :: String -> a -> StrMap a -> StrMap a');
+
+  eq(S.insert('a', 1, {}), {a: 1});
+  eq(S.insert('b', 2, {a: 1}), {a: 1, b: 2});
+  eq(S.insert('c', 3, {a: 1, b: 2, c: 4}), {a: 1, b: 2, c: 3});
+
+  jsc.assert(jsc.forall(jsc.string, jsc.number, jsc.dict(jsc.number), function(key, val, map) {
+    var insert = S.insert(key, val);
+    return S.equals(insert(insert(map)), insert(map));
+  }), {tests: 1000});
+
+});
diff --git a/test/remove.js b/test/remove.js
new file mode 100644
index 0000000..6545bc0
--- /dev/null
+++ b/test/remove.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var jsc = require('jsverify');
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('remove', function() {
+
+  eq(typeof S.remove, 'function');
+  eq(S.remove.length, 2);
+  eq(S.remove.toString(), 'remove :: String -> StrMap a -> StrMap a');
+
+  eq(S.remove('a', {}), {});
+  eq(S.remove('b', {a: 1}), {a: 1});
+  eq(S.remove('c', {a: 1, b: 2, c: 3}), {a: 1, b: 2});
+
+  jsc.assert(jsc.forall(jsc.string, jsc.dict(jsc.number), function(key, map) {
+    var remove = S.remove(key);
+    return S.equals(remove(remove(map)), remove(map));
+  }), {tests: 1000});
+
+});