Repository: calmm-js/partial.lenses
Pull request #27 (2016-05-24 16:12:05)

Adding naive collect for traversals and handling empty arrays


Patch:
diff --git a/README.md b/README.md
index aeaa290..40042f1 100644
--- a/README.md
+++ b/README.md
@@ -870,7 +870,7 @@ this does not change the behavior of the lens on undefined values.
 `L.toRamda(plens)` converts the given partial lens to a Ramda lens.  Note that
 this does not change the behavior of the lens on undefined values.
 
-### Traversal combinators
+### Traversal combinators and operations
 
 Aside from lenses, there is experimental support for traversals.  Traversals and
 lenses can be composed and the result is a traversal.  A traversal operates over
@@ -879,6 +879,23 @@ a collection of focuses and for this reason traversals cannot be viewed
 removed.  Traversals (and lenses) can also be folded over (or reduced), but such
 an operation is currently not provided.
 
+#### <a name="collect"></a>[`L.collect(t, s)`](#get "L.collect :: PTraversal s a -> Maybe s -> [a]")
+
+**`L.collect` is experimental and might be removed, renamed or changed
+semantically before next major release.**
+
+`L.collect(t, s)` returns an array of the elements focused on by the given
+traversal or lens from a data structure.  Given a lens, there will be 0 or 1
+elements in the returned array.  Given a traversal, there can be any number of
+elements in the returned array.
+
+For example:
+
+```js
+L.collect(P("xs", L.sequence, "x"), {xs: [{x: 1}, {x: 2}]})
+// [ 1, 2 ]
+```
+
 #### <a name="sequence"></a>[`L.sequence`](#sequence "L.sequence :: PTraversal [a] a")
 
 **`L.sequence` is experimental and might be removed, renamed or changed
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index d834532..edfef50 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -12,8 +12,10 @@ Identity.prototype.ap = function (x) {return new Identity(this.value(x.value))}
 
 function Constant(value) {this.value = value}
 const Const = x => new Constant(x)
+const Collect = x => new Constant([x])
 Constant.prototype.map = function () {return this}
 Constant.prototype.of = Const
+Constant.prototype.ap = function (x) {return new Const(R.concat(this.value, x.value))}
 
 //
 
@@ -88,6 +90,11 @@ const toPartial = transform => x => undefined === x ? x : transform(x)
 
 //
 
+const filtered =
+  toPartial(R.pipe(R.filter(x => x !== undefined), dropped))
+
+//
+
 const conserve = (c1, c0) => R.equals(c1, c0) ? c0 : c1
 
 const toConserve = f => (y, c0) => conserve(f(y, c0), c0)
@@ -123,11 +130,15 @@ const getI = (l, s) => l(Const)(s).value
 const modifyI = (l, x2x, s) => l(y => Ident(x2x(y)))(s).value
 const lensI = (getter, setter) => toFn => target =>
   toFn(getter(target)).map(focus => setter(focus, target))
+const collectI = (l, s) => l(Collect)(s).value
 
 export const lens = R.curry(lensI)
 export const modify = R.curry((l, x2x, s) => modifyI(toRamda(l), x2x, s))
 export const set = R.curry((l, x, s) => setI(toRamda(l), x, s))
 export const get = R.curry((l, s) => getI(toRamda(l), s))
+export const collect = R.curry((l, s) =>
+  warn("`collect` is experimental and might be removed, renamed or changed semantically before next major release") ||
+  mkArray(filtered(collectI(toRamda(l), s))))
 
 export const chain = R.curry((x2yL, xL) =>
   compose(xL, choose(xO => xO === undefined ? nothing : x2yL(xO))))
@@ -278,7 +289,7 @@ export const log = (...labels) =>
 
 export const sequence = toApplicative => target =>
   warn("`sequence` is experimental and might be removed, renamed or changed semantically before next major release") ||
-  R.sequence(Ident, R.map(toApplicative, target))
-  .map(R.pipe(R.filter(x => x !== undefined), dropped))
+  R.traverse(Ident, toApplicative, mkArray(target))
+  .map(filtered)
 
 export default compose


Test patch:
diff --git a/test/tests.js b/test/tests.js
index cc14470..3f823a8 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -250,6 +250,16 @@ describe("L.sequence", () => {
   testEq('L.set(P("xs", L.sequence, "x", L.sequence), 101, {xs: [{x: [1]}, {x: [2,3,4]}]})', {xs: [{x: [101]}, {x: [101,101,101]}]})
   testEq('L.remove(P("xs", L.sequence, "x", L.sequence), {ys: "hip", xs: [{x: [1]}, {x: [2,3,4]}]})', {ys: "hip"})
   testEq('L.modify(P("xs", L.sequence, "x"), x => x < 2 ? undefined : x, {xs: [{x:3},{x:1},{x:4},{x:1,y:0},{x:5},{x:9},{x:2}]})', {xs:[{x:3},{x:4},{y:0},{x:5},{x:9},{x:2}]})
+  testEq('L.modify(P(L.sequence, "x", L.sequence), R.add(1), [{x: [1]}, {}, {x: []}, {x: [2, 3]}])', [{x: [2]}, {x: [3, 4]}])
+  testEq('L.modify(P(L.sequence, "x", L.sequence), R.add(1), [{x: [1]}, {y: "keep"}, {x: [], z: "these"}, {x: [2, 3]}])', [{x: [2]}, {y: "keep"}, {z: "these"}, {x: [3, 4]}])
+})
+
+describe("L.collect", () => {
+  testEq('L.collect(P("xs", L.sequence, "x", L.sequence), {xs: [{x:[3,1]},{x:[4,1]},{x:[5,9,2]}]})', [3,1,4,1,5,9,2])
+  testEq('L.collect(P(L.sequence, "x", L.sequence), [{x: [1]}, {}, {x: []}, {x: [2, 3]}])', [1, 2, 3])
+  testEq('L.collect(L.sequence, [])', [])
+  testEq('L.collect("x", {x: 101})', [101])
+  testEq('L.collect("y", {x: 101})', [])
 })
 
 describe("L.pick", () => {