Repository: calmm-js/partial.lenses
Pull request #22 (2016-04-28 04:59:09)

Experimental `sequence` traversal.


Patch:
diff --git a/README.md b/README.md
index ff828bb..57ff0c7 100644
--- a/README.md
+++ b/README.md
@@ -846,6 +846,29 @@ 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
+
+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
+a collection of focuses and for this reason traversals cannot be viewed
+([`get`](#get) does not work on a traversal), but they can be modified, set and
+removed.  Traversals (and lenses) can also be folded over (or reduced), but such
+an operation is currently not provided.
+
+#### <a name="sequence"></a>[`L.sequence`](#sequence "L.sequence :: Traversal s s")
+
+**`L.sequence` is experimental and might be removed, renamed or changed
+semantically before next major release.**
+
+`L.sequence` is a traversal (rather than a lens) over an array.
+
+For example:
+
+```js
+L.modify(P("xs", L.sequence, "x"), R.add(1), {xs: [{x: 1}, {x: 2}]})
+// { xs: [ { x: 2 }, { x: 3 } ] }
+```
+
 ### Debugging
 
 #### <a name="log"></a>[`L.log(...labels)`](#log "L.log :: (...Any) -> Lens s s")
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index 1c090cd..8e2f2e3 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -6,6 +6,7 @@ function Identity(value) {this.value = value}
 const Ident = x => new Identity(x)
 Identity.prototype.map = function (x2y) {return new Identity(x2y(this.value))}
 Identity.prototype.of = Ident
+Identity.prototype.ap = function (x) {return new Identity(this.value(x.value))}
 
 //
 
@@ -256,4 +257,9 @@ const show = (...labels) => x => console.log(...labels, x) || x
 export const log = (...labels) =>
   lensI(show(...labels, "get"), show(...labels, "set"))
 
+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))
+
 export default compose


Test patch:
diff --git a/test/tests.js b/test/tests.js
index 00711ed..5d81246 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -214,6 +214,13 @@ describe("L.augment", () => {
   testEq('L.remove(L.augment({z: c => c.x + c.y}), {x: 1, y: 2})', undefined)
 })
 
+describe("L.sequence", () => {
+  testEq('L.modify(P("xs", L.sequence, "x", L.sequence), R.add(1), {xs: [{x: [1]}, {x: [2,3,4]}]})', {xs: [{x: [2]}, {x: [3,4,5]}]})
+  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}]})
+})
+
 describe("L.pick", () => {
   testEq('L.get(L.pick({x: "c"}), {a: [2], b: 1})', undefined)
   testEq('L.set(P(L.pick({x: "c"}), "x"), 4, {a: [2], b: 1})', {a: [2], b: 1, c: 4})