Repository: calmm-js/partial.lenses
Pull request #60 (2017-03-02 20:20:07)

More lazy folds


Patch:
diff --git a/README.md b/README.md
index fb36a0b..4153483 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,9 @@ parts.  [▶ Try Lenses!](https://calmm-js.github.io/partial.lenses/)
       * ~~[`L.merge(monoid, traversal, maybeData) ~> traversal`](#L-merge "L.merge: Monoid a -> (PTraversal s a -> Maybe s -> a)")~~
       * ~~[`L.mergeAs((maybeValue, index) => value, monoid, traversal, maybeData) ~> traversal`](#L-mergeAs "L.mergeAs: ((Maybe a, Index) -> r) -> Monoid r -> (PTraversal s a -> Maybe s -> r)")~~
     * [Folds over traversals](#folds-over-traversals)
+      * [`L.all((maybeValue, index) => value, traversal, maybeData) ~> boolean`](#L-all "L.all: ((Maybe a, Index) -> Boolean) -> PTraversal s a -> Boolean")
+      * [`L.and(traversal, maybeData) ~> boolean`](#L-and "L.or: PTraversal s Boolean -> Boolean")
+      * [`L.any((maybeValue, index) => value, traversal, maybeData) ~> boolean`](#L-any "L.any: ((Maybe a, Index) -> Boolean) -> PTraversal s a -> Boolean")
       * [`L.collect(traversal, maybeData) ~> [...values]`](#L-collect "L.collect: PTraversal s a -> Maybe s -> [a]")
       * [`L.collectAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> [...values]`](#L-collectAs "L.collectAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> [b]")
       * [`L.first(traversal, maybeData) ~> maybeValue`](#L-first "L.first: PTraversal s a -> Maybe s -> Maybe a")
@@ -67,6 +70,7 @@ parts.  [▶ Try Lenses!](https://calmm-js.github.io/partial.lenses/)
       * [`L.foldr((value, maybeValue, index) => value, value, traversal, maybeData) ~> value`](#L-foldr "L.foldr: ((r, Maybe a, Index) -> r) -> r -> PTraversal s a -> Maybe s -> r")
       * [`L.maximum(traversal, maybeData) ~> maybeValue`](#L-maximum "L.maximum: Ord a => PTraversal s a -> Maybe s -> Maybe a")
       * [`L.minimum(traversal, maybeData) ~> maybeValue`](#L-minimum "L.minimum: Ord a => PTraversal s a -> Maybe s -> Maybe a")
+      * [`L.or(traversal, maybeData) ~> boolean`](#L-or "L.or: PTraversal s Boolean -> Boolean")
       * [`L.product(traversal, maybeData) ~> number`](#L-product "L.product: PTraversal s Number -> Maybe s -> Number")
       * [`L.sum(traversal, maybeData) ~> number`](#L-sum "L.sum: PTraversal s Number -> Maybe s -> Number")
     * [Creating new traversals](#creating-new-traversals)
@@ -1068,6 +1072,59 @@ See also: [`L.concatAs`](#L-concatAs).
 
 #### Folds over traversals
 
+##### <a name="L-all"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-all) [`L.all((maybeValue, index) => value, traversal, maybeData) ~> boolean`](#L-all "L.all: ((Maybe a, Index) -> Boolean) -> PTraversal s a -> Boolean")
+
+**WARNING: Lazy folds over traversals are experimental.**
+
+`L.all` determines whether all of the elements focused on by the given traversal
+satisfy the given predicate.
+
+For example:
+
+```js
+L.all(x => 1 <= x && x <= 6,
+      flatten,
+      [[[1], 2], {y: 3}, [{l: 4, r: [5]}, {x: 6}]])
+// true
+```
+
+See also: [`L.any`](#L-any) and [`L.firstAs`](#L-firstAs).
+
+##### <a name="L-and"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-and) [`L.and(traversal, maybeData) ~> boolean`](#L-and "L.or: PTraversal s Boolean -> Boolean")
+
+**WARNING: Lazy folds over traversals are experimental.**
+
+`L.and` determines whether all of the elements focused on by the given traversal
+are truthy.
+
+For example:
+
+```js
+L.and(L.elems, [])
+// true
+```
+
+Note that `L.and` is equivalent to [`L.all(R.identity)`](#L-all).  See
+also: [`L.or`](#L-or).
+
+##### <a name="L-any"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-any) [`L.any((maybeValue, index) => value, traversal, maybeData) ~> boolean`](#L-any "L.any: ((Maybe a, Index) -> Boolean) -> PTraversal s a -> Boolean")
+
+**WARNING: Lazy folds over traversals are experimental.**
+
+`L.any` determines whether any of the elements focused on by the given traversal
+satisfy the given predicate.
+
+For example:
+
+```js
+L.any(x => x > 5,
+      flatten,
+      [[[1], 2], {y: 3}, [{l: 4, r: [5]}, {x: 6}]])
+// true
+```
+
+See also: [`L.all`](#L-all) and [`L.firstAs`](#L-firstAs).
+
 ##### <a name="L-collect"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-collect) [`L.collect(traversal, maybeData) ~> [...values]`](#L-collect "L.collect: PTraversal s a -> Maybe s -> [a]")
 
 `L.collect` returns an array of the defined elements focused on by the given
@@ -1125,7 +1182,7 @@ above naïve implementation.
 
 ##### <a name="L-first"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-first) [`L.first(traversal, maybeData) ~> maybeValue`](#L-first "L.first: PTraversal s a -> Maybe s -> Maybe a")
 
-**WARNING: `L.first` and [`L.firstAs`](#L-firstAs) are experimental features.**
+**WARNING: Lazy folds over traversals are experimental.**
 
 `L.first` goes lazily over the elements focused on by the given traversal and
 returns the first non-`undefined` element.
@@ -1139,7 +1196,7 @@ Note that `L.first` is equivalent to [`L.firstAs(R.identity)`](#L-firstAs).
 
 ##### <a name="L-firstAs"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-firstAs) [`L.firstAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> maybeValue`](#L-firstAs "L.firstAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> Maybe b")
 
-**WARNING: [`L.first`](#L-first) and `L.firstAs` are experimental features.**
+**WARNING: Lazy folds over traversals are experimental.**
 
 `L.firstAs` goes lazily over the elements focused on by the given traversal,
 applying the given function to each element, and returns the first
@@ -1220,6 +1277,23 @@ L.minimum(L.elems, [1,2,3])
 // 1
 ```
 
+##### <a name="L-or"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-or) [`L.or(traversal, maybeData) ~> boolean`](#L-or "L.or: PTraversal s Boolean -> Boolean")
+
+**WARNING: Lazy folds over traversals are experimental.**
+
+`L.or` determines whether any of the elements focused on by the given traversal
+is truthy.
+
+For example:
+
+```js
+L.or(L.elems, [])
+// false
+```
+
+Note that `L.or` is equivalent to [`L.any(R.identity)`](#L-any).  See
+also: [`L.and`](#L-and).
+
 ##### <a name="L-product"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#-product) [`L.product(traversal, maybeData) ~> number`](#L-product "L.product: PTraversal s Number -> Maybe s -> Number")
 
 `L.product` computes the product of the optional numbers targeted by the
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index 6fa015b..71588da 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -16,6 +16,7 @@ import {
   isString,
   keys,
   object0,
+  pipe2U,
   sndU
 } from "infestines"
 
@@ -79,9 +80,17 @@ const run = (o, C, xi2yC, s, i) => toFunction(o)(C, xi2yC, s, i)
 //
 
 const expectedOptic = "Expecting an optic"
+const header = "partial.lenses: "
+
+function warn(f, m) {
+  if (!f.warned) {
+    f.warned = 1
+    console.warn(header + m)
+  }
+}
 
 function errorGiven(m, o) {
-  console.error("partial.lenses:", m, "- given:", o)
+  console.error(header + m + " - given:", o)
   throw new Error(m)
 }
 
@@ -157,8 +166,18 @@ function the(v) {
   return result
 }
 
+const T = the(true)
+const not = x => !x
+
 const First = ConcatOf((l, r) => l && l() || r && r(), void 0, id)
 
+const mkFirst = toM => (xi2yM, t, s) => {
+  if (process.env.NODE_ENV !== "production")
+    warn(mkFirst, "Lazy folds over traversals are experimental")
+  return (s = run(t, First, pipe2U(xi2yM, toM), s),
+          s && (s = s()) && s.v)
+}
+
 //
 
 const traversePartialIndexLazy = (map, ap, z, delay, xi2yA, xs, i, n) =>
@@ -562,40 +581,28 @@ export const concatAs = curryN(4, (xMi2y, m) => {
 
 export const concat = concatAs(id)
 
-export const mergeAs = process.env.NODE_ENV === "production" ? concatAs : (f, m, t, d) => {
-  if (!mergeAs.warned) {
-    mergeAs.warned = 1
-    console.warn("partial.lenses: `mergeAs` is obsolete, just use `concatAs`")
-  }
-  return concatAs(f, m, t, d)
-}
+export const mergeAs = process.env.NODE_ENV === "production" ? concatAs : (f, m, t, d) =>
+  warn(mergeAs, "`mergeAs` is obsolete, just use `concatAs`") ||
+  concatAs(f, m, t, d)
 
-export const merge = process.env.NODE_ENV === "production" ? concat : (m, t, d) => {
-  if (!merge.warned) {
-    merge.warned = 1
-    console.warn("partial.lenses: `merge` is obsolete, just use `concat`")
-  }
-  return concat(m, t, d)
-}
+export const merge = process.env.NODE_ENV === "production" ? concat : (m, t, d) =>
+  warn(merge, "`merge` is obsolete, just use `concat`") ||
+  concat(m, t, d)
 
 // Folds over traversals
 
+export const all = pipe2U(mkFirst(x => x ? void 0 : T), not)
+
+export const and = all(id)
+
+export const any = pipe2U(mkFirst(x => x ? T : void 0), Boolean)
+
 export const collectAs = curry((xi2y, t, s) =>
   toArray(run(t, Collect, xi2y, s)) || [])
 
 export const collect = collectAs(id)
 
-export const firstAs = curry((xi2yM, t, s) => {
-  if (process.env.NODE_ENV !== "production" && !firstAs.warned) {
-    firstAs.warned = 1
-    console.warn("partial.lenses: `first` and `firstAs` are experimental features.")
-  }
-  return (s = run(t,
-                  First,
-                  (x, i) => (x = xi2yM(x, i), void 0 !== x ? the(x) : x),
-                  s),
-          s && (s = s()) && s.v)
-})
+export const firstAs = curry(mkFirst(x => void 0 !== x ? the(x) : x))
 
 export const first = firstAs(id)
 
@@ -615,6 +622,8 @@ export const maximum = concat(Mum((x, y) => x > y))
 
 export const minimum = concat(Mum((x, y) => x < y))
 
+export const or = any(id)
+
 export const product = concatAs(unto(1), Monoid((y, x) => x * y, 1))
 
 export const sum = concatAs(unto(0), Monoid((y, x) => x + y, 0))
@@ -824,21 +833,13 @@ export const orElse =
 
 // Read-only mapping
 
-export const to = process.env.NODE_ENV === "production" ? id : wi2x => {
-  if (!to.warned) {
-    to.warned = 1
-    console.warn("partial.lenses: `to` is obsolete, you can directly `compose` plain functions with optics")
-  }
-  return wi2x
-}
+export const to = process.env.NODE_ENV === "production" ? id : wi2x =>
+  warn(to, "`to` is obsolete, you can directly `compose` plain functions with optics") ||
+  wi2x
 
-export const just = process.env.NODE_ENV === "production" ? always : x => {
-  if (!just.warned) {
-    just.warned = 1
-    console.warn("partial.lenses: `just` is obsolete, just use e.g. `R.always`")
-  }
-  return always(x)
-}
+export const just = process.env.NODE_ENV === "production" ? always : x =>
+  warn(just, "`just` is obsolete, just use e.g. `R.always`") ||
+  always(x)
 
 // Transforming data
 


Test patch:
diff --git a/test/core.js b/test/core.js
index 6c54a68..0cd17d1 100644
--- a/test/core.js
+++ b/test/core.js
@@ -73,6 +73,12 @@ export const mergeAs = concatAs
 
 // Folds over traversals
 
+export const all = L.all
+
+export const and = all(R.identity)
+
+export const any = L.any
+
 export const firstAs = L.firstAs
 
 export const first = L.firstAs(R.identity)
@@ -87,6 +93,8 @@ export const collectAs = R.curry((to, t, s) =>
 export const maximum = concat({empty: () => {}, concat: maxPartial})
 export const minimum = concat({empty: () => {}, concat: minPartial})
 
+export const or = any(R.identity)
+
 export const product = concatDefined({empty: () => 1, concat: R.multiply})
 export const sum = concatDefined({empty: () => 0, concat: R.add})
 
diff --git a/test/tests.js b/test/tests.js
index 30077ad..e5b335b 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -749,7 +749,7 @@ describe("seq", () => {
          ["a", "b", "c"])
 })
 
-describe("firstAs", () => {
+describe("lazy folds", () => {
   testEq(`L.first(L.elems, [])`, undefined)
   testEq(`L.first(L.values, {})`, undefined)
   testEq(`L.firstAs((x, i) => x > 3 ? [x + 2, i] : undefined,
@@ -765,4 +765,11 @@ describe("firstAs", () => {
                     flatten,
                     [[[1], 2], {y: 3}, [{l: 41, r: [5]}, {x: 6}]])`,
         [41])
+
+  testEq(`L.any((x, i) => x > i, L.elems, [0,1,3])`, true)
+  testEq(`L.any((x, i) => x > i, L.elems, [0,1,2])`, false)
+  testEq(`L.all((x, i) => x > i, L.elems, [1,2,3])`, true)
+  testEq(`L.all((x, i) => x > i, L.elems, [1,2,2])`, false)
+  testEq(`L.and(L.elems, [])`, true)
+  testEq(`L.or(L.elems, [])`, false)
 })
diff --git a/test/type.js b/test/type.js
index de1da00..d498a43 100644
--- a/test/type.js
+++ b/test/type.js
@@ -38,6 +38,7 @@ const instance = c => fromPredicate(x => x instanceof c)
 export const integer = fromPredicate(Number.isInteger)
 export const nonNegative = and(integer, fromPredicate(x => 0 <= x))
 
+export const boolean = type("boolean")
 export const number = type("number")
 export const string = type("string")
 export const undef = fromPredicate(x => x === undefined)
diff --git a/test/types.js b/test/types.js
index 04d4a20..7c51fce 100644
--- a/test/types.js
+++ b/test/types.js
@@ -105,6 +105,17 @@ export const merge = concat
 
 // Folds over traversals
 
+export const all = T.fn([T.fn([T_maybeData, T_index], T.any),
+                         T_traversal,
+                         T_maybeData],
+                        T.boolean)
+export const and = T.fn([T_traversal, T_maybeData], T.boolean)
+
+export const any = T.fn([T.fn([T_maybeData, T_index], T.any),
+                         T_traversal,
+                         T_maybeData],
+                        T.boolean)
+
 export const collect = T.fn([T_traversal, T_maybeData], T.array(T_data))
 export const collectAs = T.fn([T.fn([T_maybeData, T_index], T_maybeData),
                                T_traversal,
@@ -129,6 +140,8 @@ export const foldr = foldl
 export const maximum = T.fn([T_traversal, T_maybeData], T.any)
 export const minimum = T.fn([T_traversal, T_maybeData], T.any)
 
+export const or = T.fn([T_traversal, T_maybeData], T.boolean)
+
 export const product = T.fn([T_traversal, T_maybeData], T.number)
 export const sum = product