Repository: calmm-js/partial.lenses
Pull request #43 (2017-01-15 23:27:33)

Split `sequence` into `elems` and `values`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf88730..a5e8841 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Changelog
 
+## 7.3.0
+
+Deprecated `L.sequence` and introduced `L.elems`, which operates on arrays, and,
+`L.values`, which operates on objects, to be used instead.  `L.sequence`
+originally only operated on arrays, but it was generalized to operate on objects
+in 6.0.0.  Unfortunately that turned out to be a mistake, because in the next
+major version, 8.0.0, the plan is to relax the treatment of objects and array
+like objects.  The problem is that, with the generalized semantics, the type of
+the result, object or array, when writing through `L.sequence` would depend on
+the input in an uncontrollable manner.  Apologies for the inconvenience!
+
 ## 7.0.0
 
 Added minimal support for indexing.  Various operations and combinators now
diff --git a/README.md b/README.md
index 81bfc69..2b6ec81 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,9 @@ parts.  [Try Lenses!](https://calmm-js.github.io/partial.lenses/)
     * [Creating new traversals](#creating-new-traversals)
       * [`L.branch({prop: traversal, ...props})`](#L-branch "L.branch: {p1: PTraversal s a, ...pts} -> PTraversal s a")
     * [Traversals and combinators](#traversals-and-combinators)
-      * [`L.sequence`](#L-sequence "L.sequence: PTraversal ([a] | {p: a, ...ps}) a")
+      * [`L.elems`](#L-elems "L.elems: PTraversal [a] a")
+      * [`L.values`](#L-values "L.values: PTraversal {p: a, ...ps} a")
+      * ~~[`L.sequence`](#L-sequence "L.sequence: PTraversal ([a] | {p: a, ...ps}) a")~~
   * [Lenses](#lenses)
     * [Operations on lenses](#operations-on-lenses)
       * [`L.get(lens, maybeData)`](#L-get "L.get: PLens s a -> Maybe s -> Maybe a")
@@ -339,14 +341,14 @@ that targets all the texts:
 ```js
 const texts = ["contents",
                L.required([]),
-               L.sequence,
+               L.elems,
                L.choose(R.pipe(L.remove("text"), L.defaults)),
                "text"]
 ```
 
-What makes the above a traversal is the [`L.sequence`](#L-sequence) part.  Once
-a traversal is composed with a lens, the whole results is a traversal.  The
-other parts of the above composition should already be familiar from previous
+What makes the above a traversal is the [`L.elems`](#L-elems) part.  Once a
+traversal is composed with a lens, the whole results is a traversal.  The other
+parts of the above composition should already be familiar from previous
 examples.  Note the use of [`L.choose`](#L-choose)
 with [`L.defaults`](#L-choose).  We'll get back to that shortly.
 
@@ -445,7 +447,7 @@ L.modify(["elems", 0, "x"], R.inc, {elems: [{x: 1, y: 2}, {x: 3, y: 4}]})
 or, when using a [traversal](#traversals), elements
 
 ```js
-L.modify(["elems", L.sequence, "x"],
+L.modify(["elems", L.elems, "x"],
          R.dec,
          {elems: [{x: 1, y: 2}, {x: 3, y: 4}]})
 // { elems: [ { x: 0, y: 2 }, { x: 2, y: 4 } ] }
@@ -466,7 +468,7 @@ L.remove([0, "x"], [{x: 1}, {x: 2}, {x: 3}])
 or, when using a [traversal](#traversals), elements
 
 ```js
-L.remove([L.sequence, "x", L.when(x => x > 1)], [{x: 1}, {x: 2, y: 1}, {x: 3}])
+L.remove([L.elems, "x", L.when(x => x > 1)], [{x: 1}, {x: 2, y: 1}, {x: 3}])
 // [ { x: 1 }, { y: 1 } ]
 ```
 
@@ -488,7 +490,7 @@ L.set(["a", 0, "x"], 11, {id: "z"})
 or, when using a [traversal](#traversals), elements
 
 ```js
-L.set([L.sequence, "x", L.when(x => x > 1)], -1, [{x: 1}, {x: 2, y: 1}, {x: 3}])
+L.set([L.elems, "x", L.when(x => x > 1)], -1, [{x: 1}, {x: 2, y: 1}, {x: 3}])
 // [ { x: 1 }, { x: -1, y: 1 }, { x: -1 } ]
 ```
 
@@ -563,7 +565,7 @@ like [`L.zero`](#L-zero), which is the identity element of `L.choice`.
 For example:
 
 ```js
-L.modify([L.sequence, L.choice("a", "d")], R.inc, [{R: 1}, {a: 1}, {d: 2}])
+L.modify([L.elems, L.choice("a", "d")], R.inc, [{R: 1}, {a: 1}, {d: 2}])
 // [ { R: 1 }, { a: 2 }, { d: 3 } ]
 ```
 
@@ -605,14 +607,14 @@ the focus is `undefined`, the lens will be read-only.
 As an example, consider the difference between:
 
 ```js
-L.set([L.sequence, "x"], 3, [{x: 1}, {y: 2}])
+L.set([L.elems, "x"], 3, [{x: 1}, {y: 2}])
 // [ { x: 3 }, { y: 2, x: 3 } ]
 ```
 
 and:
 
 ```js
-L.set([L.sequence, "x", L.optional], 3, [{x: 1}, {y: 2}])
+L.set([L.elems, "x", L.optional], 3, [{x: 1}, {y: 2}])
 // [ { x: 3 }, { y: 2 } ]
 ```
 
@@ -627,7 +629,7 @@ selectively turn a lens into a read-only lens whose view is `undefined`.
 For example:
 
 ```js
-L.modify([L.sequence, L.when(x => x > 0)], R.negate, [0, -1, 2, -3, 4])
+L.modify([L.elems, L.when(x => x > 0)], R.negate, [0, -1, 2, -3, 4])
 // [ 0, -1, -2, -3, -4 ]
 ```
 
@@ -644,8 +646,8 @@ read-only lens whose view is always `undefined`.
 For example:
 
 ```js
-L.collect([L.sequence,
-           L.choose(x => (R.is(Array, x) ? L.sequence :
+L.collect([L.elems,
+           L.choose(x => (R.is(Array, x) ? L.elems :
                           R.is(Object, x) ? "x" :
                           L.zero))],
           [1, {x: 2}, [3,4]])
@@ -665,8 +667,11 @@ data structure of nested arrays and objects:
 
 ```js
 const flatten = [L.optional, L.lazy(rec => {
-  const nest = [L.sequence, rec]
-  return L.choose(x => R.is(Object, x) ? nest : L.identity)
+  const elems = [L.elems, rec]
+  const values = [L.values, rec]
+  return L.choose(x => (x instanceof Array ? elems :
+                        x instanceof Object ? values :
+                        L.identity))
 })]
 ```
 
@@ -782,7 +787,7 @@ For example:
 
 ```js
 const Sum = {empty: () => 0, concat: (x, y) => x + y}
-L.concat(Sum, L.sequence, [1, 2, 3])
+L.concat(Sum, L.elems, [1, 2, 3])
 // 6
 ```
 
@@ -805,7 +810,7 @@ the values returned by `xMi2r`.
 For example:
 
 ```js
-L.concatAs(x => x, Sum, L.sequence, [1, 2, 3])
+L.concatAs(x => x, Sum, L.elems, [1, 2, 3])
 // 6
 ```
 
@@ -831,7 +836,7 @@ the values returned by `xMi2r`.
 For example:
 
 ```js
-L.foldMapOf(Sum, L.sequence, x => x, [1, 2, 3])
+L.foldMapOf(Sum, L.elems, x => x, [1, 2, 3])
 // 6
 ```
 
@@ -848,7 +853,7 @@ the values focused on by `t`.
 For example:
 
 ```js
-L.merge(Sum, L.sequence, [1, 2, 3])
+L.merge(Sum, L.elems, [1, 2, 3])
 // 6
 ```
 
@@ -871,7 +876,7 @@ the values returned by `xMi2r`.
 For example:
 
 ```js
-L.mergeAs(x => x, Sum, L.sequence, [1, 2, 3])
+L.mergeAs(x => x, Sum, L.elems, [1, 2, 3])
 // 6
 ```
 
@@ -893,7 +898,7 @@ traversal or lens from a data structure.
 For example:
 
 ```js
-L.collect(["xs", L.sequence, "x"], {xs: [{x: 1}, {x: 2}]})
+L.collect(["xs", L.elems, "x"], {xs: [{x: 1}, {x: 2}]})
 // [ 1, 2 ]
 ```
 
@@ -913,7 +918,7 @@ traversal, there can be any number of elements in the array returned by
 For example:
 
 ```js
-L.collectAs(R.negate, ["xs", L.sequence, "x"], {xs: [{x: 1}, {x: 2}]})
+L.collectAs(R.negate, ["xs", L.elems, "x"], {xs: [{x: 1}, {x: 2}]})
 // [ -1, -2 ]
 ```
 
@@ -932,7 +937,7 @@ So:
 ```js
 L.concatAs(R.pipe(R.negate, toCollect),
            Collect,
-           ["xs", L.sequence, "x"],
+           ["xs", L.elems, "x"],
            {xs: [{x: 1}, {x: 2}]})
 // [ -1, -2 ]
 ```
@@ -956,7 +961,7 @@ Given a traversal, there can be any number of elements in the array returned by
 For example:
 
 ```js
-L.collectMap(["xs", L.sequence, "x"], R.negate, {xs: [{x: 1}, {x: 2}]})
+L.collectMap(["xs", L.elems, "x"], R.negate, {xs: [{x: 1}, {x: 2}]})
 // [ -1, -2 ]
 ```
 
@@ -975,7 +980,7 @@ So:
 ```js
 L.concatAs(R.pipe(R.negate, toCollect),
            Collect,
-           ["xs", L.sequence, "x"],
+           ["xs", L.elems, "x"],
            {xs: [{x: 1}, {x: 2}]})
 // [ -1, -2 ]
 ```
@@ -991,7 +996,7 @@ traversal.
 For example:
 
 ```js
-L.foldl((x, y) => x + y, 0, L.sequence, [1,2,3])
+L.foldl((x, y) => x + y, 0, L.elems, [1,2,3])
 // 6
 ```
 
@@ -1003,7 +1008,7 @@ traversal.
 For example:
 
 ```js
-L.foldr((x, y) => x * y, 1, L.sequence, [1,2,3])
+L.foldr((x, y) => x * y, 1, L.elems, [1,2,3])
 // 6
 ```
 
@@ -1015,7 +1020,7 @@ traversal.
 For example:
 
 ```js
-L.maximum(L.sequence, [1,2,3])
+L.maximum(L.elems, [1,2,3])
 // 3
 ```
 
@@ -1027,7 +1032,7 @@ traversal.
 For example:
 
 ```js
-L.minimum(L.sequence, [1,2,3])
+L.minimum(L.elems, [1,2,3])
 // 1
 ```
 
@@ -1039,7 +1044,7 @@ traversal.
 For example:
 
 ```js
-L.product(L.sequence, [1,2,3])
+L.product(L.elems, [1,2,3])
 // 6
 ```
 
@@ -1050,7 +1055,7 @@ L.product(L.sequence, [1,2,3])
 For example:
 
 ```js
-L.sum(L.sequence, [1,2,3])
+L.sum(L.elems, [1,2,3])
 // 6
 ```
 
@@ -1064,7 +1069,7 @@ how the new traversal should visit the properties of an object.
 For example:
 
 ```js
-L.collect(L.branch({first: L.sequence, second: L.identity}),
+L.collect(L.branch({first: L.elems, second: L.identity}),
           {first: ["x"], second: "y"})
 // [ 'x', 'y' ]
 ```
@@ -1085,8 +1090,34 @@ See the [BST traversal](#bst-traversal) section for a more meaningful example.
 
 #### Traversals and combinators
 
+##### <a name="L-elems"></a> [≡](#contents) [`L.elems`](#L-elems "L.elems: PTraversal [a] a")
+
+`L.elems` is a traversal over the elements of an array.
+
+For example:
+
+```js
+L.modify(["xs", L.elems, "x"], R.inc, {xs: [{x: 1}, {x: 2}]})
+// { xs: [ { x: 2 }, { x: 3 } ] }
+```
+
+##### <a name="L-values"></a> [≡](#contents) [`L.values`](#L-values "L.values: PTraversal {p: a, ...ps} a")
+
+`L.values` is a traversal over the values of an object.
+
+For example:
+
+```js
+L.modify(L.values, R.negate, {a: 1, b: 2, c: 3})
+// { a: -1, b: -2, c: -3 }
+```
+
 ##### <a name="L-sequence"></a> [≡](#contents) [`L.sequence`](#L-sequence "L.sequence: PTraversal ([a] | {p: a, ...ps}) a")
 
+**NOTE: `L.sequence` has been deprecated and will be removed in the next major
+version.  Use [`L.elems`](#L-elems) when operating on arrays
+and [`L.values`](#L-values) when operating on (other) objects..**
+
 `L.sequence` is a traversal over the values of an array or object.
 
 For example:
@@ -1992,7 +2023,7 @@ const isoList = L.iso(fromList, toList)
 So, now we can [compose](#L-compose) a traversal over `List` as:
 
 ```js
-const seqList = [isoList, L.sequence]
+const seqList = [isoList, L.elems]
 ```
 
 And all the usual operations work as one would expect, for example:
diff --git a/bench/bench.js b/bench/bench.js
index 07cf925..a441eb4 100644
--- a/bench/bench.js
+++ b/bench/bench.js
@@ -51,7 +51,7 @@ const toList = x => x !== undefined ? [x] : []
 const d0x0y = [L.defaults([]), 0, "x", 0, "y"]
 
 const flatten = [L.optional, L.lazy(rec => {
-  const nest = L.toFunction([L.sequence, rec])
+  const nest = L.toFunction([L.elems, rec])
   return L.choose(x => I.isArray(x) ? nest : L.identity)
 })]
 
@@ -103,39 +103,39 @@ R.forEach(bs => {
   s.run()
 }, [
   [
-    `L.foldr(add, 0, L.sequence, xs100)`,
+    `L.foldr(add, 0, L.elems, xs100)`,
     `O.Fold.foldrOf(O.Traversal.traversed, addC, 0, xs100)`,
     `R.reduceRight(add, 0, xs100)`,
   ], [
-    `L.foldr(add, 0, L.sequence, xs100000)`,
+    `L.foldr(add, 0, L.elems, xs100000)`,
     `O.Fold.foldrOf(O.Traversal.traversed, addC, 0, xs100000)`,
     `R.reduceRight(add, 0, xs100000)`,
   ], [
-    `L.foldl(add, 0, L.sequence, xs100)`,
+    `L.foldl(add, 0, L.elems, xs100)`,
     `O.Fold.foldlOf(O.Traversal.traversed, addC, 0, xs100)`,
     `R.reduce(add, 0, xs100)`,
   ], [
-    'L.merge(Sum, L.sequence, xs100)',
-    'L.sum(L.sequence, xs100)',
+    'L.merge(Sum, L.elems, xs100)',
+    'L.sum(L.elems, xs100)',
     'O.Fold.sumOf(O.Traversal.traversed, xs100)',
     'P.sumOf(P.traversed, xs100)',
     'R.sum(xs100)',
   ], [
-    'L.maximum(L.sequence, xs100)',
+    'L.maximum(L.elems, xs100)',
     'O.Fold.maximumOf(O.Traversal.traversed, xs100)',
   ], [
-    'L.merge(Sum, [L.sequence, L.sequence, L.sequence], xsss100)',
-    'L.sum([L.sequence, L.sequence, L.sequence], xsss100)',
+    'L.merge(Sum, [L.elems, L.elems, L.elems], xsss100)',
+    'L.sum([L.elems, L.elems, L.elems], xsss100)',
     `O.Fold.sumOf(R.compose(O.Traversal.traversed,
                             O.Traversal.traversed,
                             O.Traversal.traversed),
                   xsss100)`,
     'P.sumOf(R.compose(P.traversed, P.traversed, P.traversed), xsss100)',
   ], [
-    'L.collect(L.sequence, xs100)',
+    'L.collect(L.elems, xs100)',
     'O.Fold.toListOf(O.Traversal.traversed, xs100)',
   ], [
-    'L.collect([L.sequence, L.sequence, L.sequence], xsss100)',
+    'L.collect([L.elems, L.elems, L.elems], xsss100)',
     `O.Fold.toListOf(R.compose(O.Traversal.traversed,
                                O.Traversal.traversed,
                                O.Traversal.traversed),
@@ -145,12 +145,12 @@ R.forEach(bs => {
     'L.collect(flatten, xsss100)',
     'R.flatten(xsss100)',
   ], [
-    'L.modify(L.sequence, inc, xs100)',
+    'L.modify(L.elems, inc, xs100)',
     'O.Setter.over(O.Traversal.traversed, inc, xs100)',
     'P.over(P.traversed, inc, xs100)',
     'R.map(inc, xs100)',
   ], [
-    'L.modify([L.sequence, L.sequence, L.sequence], inc, xsss100)',
+    'L.modify([L.elems, L.elems, L.elems], inc, xsss100)',
     `O.Setter.over(R.compose(O.Traversal.traversed,
                              O.Traversal.traversed,
                              O.Traversal.traversed),
@@ -242,7 +242,7 @@ R.forEach(bs => {
     'L.get(L.valueOr(1), null)',
     'L.get(L.valueOr(1), undefined)',
   ], [
-    'L.concatAs(toList, List, L.sequence, xs100)',
+    'L.concatAs(toList, List, L.elems, xs100)',
   ], [
     'L.modify(flatten, inc, xsss100)',
   ], [
diff --git a/package.json b/package.json
index 485e514..06a5bc3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "partial.lenses",
-  "version": "7.2.2",
+  "version": "7.3.0",
   "description": "Partial lenses, isomorphisms and traversals for manipulating JSON and more",
   "main": "lib/partial.lenses.js",
   "scripts": {
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index 7e34c67..32eca60 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -496,12 +496,20 @@ export function branch(template) {
 
 // Traversals and combinators
 
-export function sequence(A, xi2yA, xs, _) {
+export function elems(A, xi2yA, xs, _) {
   if (isArray(xs)) {
     return A === Ident
       ? mapPartialIndexU(xi2yA, xs)
       : traversePartialIndex(A, xi2yA, xs)
-  } else if (isObject(xs)) {
+  } else {
+    if (process.env.NODE_ENV !== "production")
+      reqApplicative(A)
+    return (0,A.of)(xs)
+  }
+}
+
+export function values(A, xi2yA, xs, _) {
+  if (isObject(xs)) {
     return branchOn(keys(xs))(A, xi2yA, xs)
   } else {
     if (process.env.NODE_ENV !== "production")
@@ -510,6 +518,14 @@ export function sequence(A, xi2yA, xs, _) {
   }
 }
 
+export function sequence(A, xi2yA, xs, i) {
+  if (process.env.NODE_ENV !== "production" && !sequence.warned) {
+    sequence.warned = 1
+    console.warn("partial.lenses: `sequence` has been deprecated and will be removed in the next major version.  Use `elems` when operating on arrays and `values` when operating on (other) objects.")
+  }
+  return (isArray(xs) ? elems : values)(A, xi2yA, xs, i)
+}
+
 // Operations on lenses
 
 export const get = curry(getU)


Test patch:
diff --git a/test/core.js b/test/core.js
index f3e1a9a..25e2917 100644
--- a/test/core.js
+++ b/test/core.js
@@ -67,6 +67,8 @@ export const sum = concatDefined({empty: () => 0, concat: R.add})
 
 export const branch = L.branch
 
+export const elems = L.elems
+export const values = L.values
 export const sequence = L.sequence
 
 // Lenses
diff --git a/test/tests.js b/test/tests.js
index d23d870..63e090a 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -367,6 +367,7 @@ describe("L.sequence", () => {
   testEq('L.remove(L.sequence, {x: 11, y: 22, z: 33})', undefined)
   testEq('L.modify(L.sequence, R.inc, {})', undefined)
   testEq('L.modify(L.sequence, R.inc, null)', null)
+  testEq('L.modify(L.elems, R.inc, null)', null)
 })
 
 describe("L.optional", () => {
diff --git a/test/types.js b/test/types.js
index b784aa2..75f52b9 100644
--- a/test/types.js
+++ b/test/types.js
@@ -106,6 +106,8 @@ export const sum = product
 
 export const branch = T.fn([T.props(T_traversal)], T_traversal)
 
+export const elems = T_traversal
+export const values = T_traversal
 export const sequence = T_traversal
 
 export const get = T.fn([T_lens, T_maybeData], T_maybeData)