Repository: calmm-js/partial.lenses
Pull request #49 (2017-01-23 10:00:34)

WIP: Stricter domain checking addresses #13

codecov-io: ## [Current coverage](https://codecov.io/gh/calmm-js/partial.lenses/pull/49?src=pr) is 100% (diff: 100%)
> Merging [#49](https://codecov.io/gh/calmm-js/partial.lenses/pull/49?src=pr) into [master](https://codecov.io/gh/calmm-js/partial.lenses/branch/master?src=pr) will not change coverage

```diff
@@           master   #49   diff @@
===================================
  Files           1     1          
  Lines         372   379     +7   
  Methods         0     0          
  Messages        0     0          
  Branches        0     0          
===================================
+ Hits          372   379     +7   
  Misses          0     0          
  Partials        0     0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [f6563eb...508ebf5](https://codecov.io/gh/calmm-js/partial.lenses/compare/f6563ebde3ab1acd59c8e78d4c420a55e43a0184...508ebf5f81a575d99354b1ec5d38609bbd4a3318?src=pr)

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9cad04..77bcc7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
 # Changelog
 
+## 9.0.0
+
+`L.augment`, `L.pick` and `L.props` can now be written with an `instanceof
+Object` or `undefined`.  Other values are considered errors.  Previously they
+could be written with anything, but only a plain `Object` was considered
+different from `undefined`.
+
+`L.slice` and `L.filter` can now be written with an array-like object or
+`undefined`.  Other values are considered errors.  This was the case earlier
+already, but now it is asserted in non-production builds.
+
+`L.index` no longer produces `null` for previously undefined elements.
+`L.index` was changed in 4.0.0 to produce `null` elements.  In 8.0.0 treatment
+of array-like objects was relaxed, but array producing optics did not
+consistently produce `null` elements.  With the relaxed semantics it seems that
+producing `null` values would complicate treatment of arrays, so it seems best
+to just produce arrays with `undefined` for previously undefined elements.
+
 ## 8.0.0
 
 Relaxed treatment of objects and array like objects.  Previously various optics
diff --git a/README.md b/README.md
index 9ee7909..c32e5d4 100644
--- a/README.md
+++ b/README.md
@@ -1398,7 +1398,7 @@ of an [array-like](#array-like) object.
   shifting all higher indices down by one.  If the result would be an empty
   array, the whole result will be `undefined`.
 * When setting a defined value to an index that is higher than the length of the
-  array-like object, the missing elements will be filled with `null`.
+  array-like object, the missing elements will be filled with `undefined`.
 
 For example:
 
@@ -2025,11 +2025,10 @@ L.set(idxList(5), "!", sampleList)
 ```
 
 Consider what happens when the index given to `idxList` points further beyond
-the last element.  The [`L.index`](#L-index) lens adds `null` values.  The above
-lens adds `undefined` values, which is not ideal with partial lenses, because of
-the special treatment of `undefined`.  In practise, however, it is not typical
-to `set` elements except to append just after the last element.  Treating the
-special case is left as an exercise for the reader.
+the last element.  Both the [`L.index`](#L-index) lens and the above lens add
+`undefined` values, which is not ideal with partial lenses, because of the
+special treatment of `undefined`.  In practise, however, it is not typical to
+`set` elements except to append just after the last element.
 
 #### Interfacing traversals
 
diff --git a/package.json b/package.json
index de2ea38..9d49556 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "partial.lenses",
-  "version": "8.1.1",
+  "version": "9.0.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 9fa4300..1361008 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -174,46 +174,33 @@ const funProp = k => (F, xi2yF, x, _) =>
 
 //
 
-function clearRange(xs, i, j) {
-  if (process.env.NODE_ENV !== "production" && !clearRange.warned) {
-    clearRange.warned=1
-    console.warn("partial.lenses: In the next major version, `index` will not set undefined elements to `null`.")
-  }
-  while (i < j)
-    xs[i++] = null
-  return xs
-}
-
 const getIndex = (i, xs) => seemsArrayLike(xs) ? xs[i] : void 0
 
 function setIndex(i, x, xs) {
   if (process.env.NODE_ENV !== "production" && i < 0)
     throw new Error("partial.lenses: Negative indices are not supported by `index`.")
+  if (!seemsArrayLike(xs))
+    xs = ""
+  const n = xs.length
   if (void 0 !== x) {
-    if (!seemsArrayLike(xs))
-      return setAt(clearRange(Array(i+1), 0, i), i, x)
-    const n = xs.length
     if (n <= i)
-      return setAt(clearRange(copyToFrom(Array(i+1), 0, xs, 0, n), n, i), i, x)
+      return setAt(copyToFrom(Array(i+1), 0, xs, 0, i), i, x)
     const ys = Array(n)
     for (let j=0; j<n; ++j)
       ys[j] = xs[j]
     ys[i] = x
     return ys
   } else {
-    if (seemsArrayLike(xs)) {
-      const n = xs.length
-      if (0 < n) {
-        if (n <= i)
-          return fromArrayLike(xs)
-        if (1 < n) {
-          const ys = Array(n-1)
-          for (let j=0; j<i; ++j)
-            ys[j] = xs[j]
-          for (let j=i+1; j<n; ++j)
-            ys[j-1] = xs[j]
-          return ys
-        }
+    if (0 < n) {
+      if (n <= i)
+        return fromArrayLike(xs)
+      if (1 < n) {
+        const ys = Array(n-1)
+        for (let j=0; j<i; ++j)
+          ys[j] = xs[j]
+        for (let j=i+1; j<n; ++j)
+          ys[j-1] = xs[j]
+        return ys
       }
     }
   }
@@ -331,14 +318,9 @@ function getPick(template, x) {
 }
 
 const setPick = (template, x) => value => {
-  if (!isObject(value)) {
-    if (process.env.NODE_ENV !== "production" && !setPick.warned &&
-        !(void 0 === value || value instanceof Object)) {
-      setPick.warned=1
-      console.warn("partial.lenses: In the next major version, `pick` can only be set with `undefined` or an instanceof Object.")
-    }
-    value = void 0
-  }
+  if (process.env.NODE_ENV !== "production" &&
+      !(void 0 === value || value instanceof Object))
+    throw new Error("partial.lenses: `pick` must be set with undefined or an object")
   for (const k in template)
     x = setU(template[k], value && value[k], x)
   return x
@@ -586,20 +568,27 @@ export const lens = curry((get, set) => (F, xi2yF, x, i) =>
 
 // Computing derived props
 
-export const augment = template => lens(
-  x => {
-    x = dissocPartialU(0, x)
-    if (x)
-      for (const k in template)
-        x[k] = template[k](x)
-    return x
-  },
-  (y, x) => {
-    if (isObject(y)) {
+export const augment = template => {
+  if (process.env.NODE_ENV !== "production" && !isObject(template))
+    throw new Error("partial.lenses: `augment` expects a plain Object template")
+  return lens(
+    x => {
+      x = dissocPartialU(0, x)
+      if (x)
+        for (const k in template)
+          x[k] = template[k](x)
+      return x
+    },
+    (y, x) => {
+      if (process.env.NODE_ENV !== "production" &&
+          !(void 0 === y || y instanceof Object))
+        throw new Error("partial.lenses: `augment` must be set with undefined or an object")
+      if (y && y.constructor !== Object)
+        y = Object.assign({}, y)
       if (!(x instanceof Object))
         x = void 0
       let z
-      const set = (k, v) => {
+      function set(k, v) {
         if (!z)
           z = {}
         z[k] = v
@@ -612,8 +601,8 @@ export const augment = template => lens(
             set(k, x[k])
       }
       return z
-    }
-  })
+    })
+}
 
 // Enforcing invariants
 
@@ -644,6 +633,9 @@ export const filter = xi2b => (F, xi2yF, xs, i) => {
     partitionIntoIndex(xi2b, xs, ts = [], fs = [])
   return (0,F.map)(
     ts => {
+      if (process.env.NODE_ENV !== "production" &&
+          !(void 0 === ts || seemsArrayLike(ts)))
+        throw new Error("partial.lenses: `filter` must be set with undefined or an array-like object")
       const tsN = ts ? ts.length : 0,
             fsN = fs ? fs.length : 0,
             n = tsN + fsN
@@ -680,6 +672,9 @@ export const slice = curry((begin, end) => (F, xsi2yF, xs, i) => {
         e = sliceIndex(b, xsN, xsN, end)
   return (0,F.map)(
     zs => {
+      if (process.env.NODE_ENV !== "production" &&
+          !(void 0 === zs || seemsArrayLike(zs)))
+        throw new Error("partial.lenses: `slice` must be set with undefined or an array-like object")
       const zsN = zs ? zs.length : 0, bPzsN = b + zsN, n = xsN - e + bPzsN
       return n
         ? copyToFrom(copyToFrom(copyToFrom(Array(n), 0, xs, 0, b),
@@ -728,8 +723,12 @@ export const just = x => to(always(x))
 
 // Transforming data
 
-export const pick = template => (F, xi2yF, x, i) =>
-  (0,F.map)(setPick(template, x), xi2yF(getPick(template, x), i))
+export const pick = template => {
+  if (process.env.NODE_ENV !== "production" && !isObject(template))
+    throw new Error("partial.lenses: `pick` expects a plain Object template")
+  return (F, xi2yF, x, i) =>
+    (0,F.map)(setPick(template, x), xi2yF(getPick(template, x), i))
+}
 
 export const replace = curry((inn, out) => {
   const o2i = x => replaced(out, inn, x)


Test patch:
diff --git a/test/tests.js b/test/tests.js
index 420bfda..63998d0 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -150,8 +150,8 @@ describe('L.index', () => {
   testEq('L.set([0], undefined, [null])', undefined)
   testEq('L.set([L.required([]), 0], undefined, [null])', [])
   testEq('L.set([1], 4, [1, 2, 3])', [1, 4, 3])
-  testEq('L.set(2, 4, undefined)', [null, null, 4])
-  testEq('L.set([2], 4, [1])', [1, null, 4])
+  testEq('L.set(2, 4, undefined)', [undefined, undefined, 4])
+  testEq('L.set([2], 4, [1])', [1, undefined, 4])
   testEq('L.remove([0], [1, 2, 3])', [2, 3])
   testEq('L.set([1], undefined, [1, 2, 3])', [1, 3])
   testEq('L.set(2, undefined, [1, 2, 3])', [1, 2])
@@ -358,6 +358,8 @@ describe("L.augment", () => {
          {x: 1, y: 2, z: 3})
   testEq('L.remove([L.augment({y: () => 1}), "x"], {x:0})', undefined)
   testEq('L.remove(L.augment({z: c => c.x + c.y}), {x: 1, y: 2})', undefined)
+  testEq('L.set(L.augment({z: c => c.x + c.y}), new XYZ(3,2,1), {x: 1, y: 2})',
+         {x: 3, y: 2})
   empties.filter(x => !R.contains(x, {})).forEach(invalid => {
     testEq(`L.get(L.augment({x: () => 1}), ${show(invalid)})`, undefined)
   })
@@ -516,7 +518,6 @@ describe("L.props", () => {
   testEq('L.set(L.props("x", "y"), {y: 4}, {x: 1, y: 2, z: 3})', {y: 4, z: 3})
   testEq('L.remove(L.props("x", "y"), {x: 1, y: 2})', undefined)
   testEq('L.set(L.props("a", "b"), {a: 2}, {a: 1, b: 3})', {a: 2})
-  testEq('L.set(L.props("length"), "lol", undefined)', undefined)
 })
 
 export const numeric = f => x => x !== undefined ? f(x) : undefined
@@ -600,6 +601,13 @@ if (process.env.NODE_ENV !== "production") {
     testThrows('X.get(L.elems, [])')
 
     testThrows('X.get(x => x, 0)')
+
+    testThrows('L.set(L.props("length"), "lol", undefined)')
+    testThrows('L.set(L.slice(undefined, undefined), 11, [])')
+    testThrows('L.pick(new XYZ(1,2,3))')
+    testThrows('L.set(L.filter(undefined, undefined), {x: 11}, [])')
+    testThrows('L.augment(new XYZ(1,2,3))')
+    testThrows('L.set(L.augment({y: () => 1}), 45, {x: 1})')
   })
 }