Repository: calmm-js/partial.lenses
Pull request #51 (2017-01-30 08:58:51)

Experiment to implicitly convert functions to getters
PR to get CI feedback.  Not to be merged lightly!


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7593afd..5892fab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 9.3.0
+
+Obsoleted `L.to` and `L.just`.  You can now directly compose optics with
+ordinary functions (whose arity is not 4) and the result is a read-only optic.
+This makes `L.to` the same as `R.identity` and `L.just` is the same as
+`R.always`.
+
 ## 9.1.0
 
 Obsoleted `L.mergeAs` and `L.merge`.  `L.concatAs` and `L.concat` are now just
diff --git a/README.md b/README.md
index cc3a1f4..d093785 100644
--- a/README.md
+++ b/README.md
@@ -97,8 +97,8 @@ parts.  [▶ Try Lenses!](https://calmm-js.github.io/partial.lenses/)
     * [Adapting to data](#adapting-to-data)
       * [`L.orElse(backupLens, primaryLens) ~> lens`](#L-orElse "L.orElse: (PLens s a, PLens s a) -> PLens s a")
     * [Read-only mapping](#read-only-mapping)
-      * [`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")
-      * [`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")
+      * ~~[`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")~~
+      * ~~[`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")~~
     * [Transforming data](#transforming-data)
       * [`L.pick({prop: lens, ...props}) ~> lens`](#L-pick "L.pick: {p1: PLens s a1, ...pls} -> PLens s {p1: a1, ...pls}")
       * [`L.replace(maybeValueIn, maybeValueOut) ~> lens`](#L-replace "L.replace: Maybe s -> Maybe s -> PLens s s")
@@ -481,8 +481,8 @@ to [`L.modify(lens, R.always(maybeValue), maybeData)`](#L-modify).
 
 ##### <a name="L-compose"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-compose) [`L.compose(...optics) ~> optic`](#L-compose "L.compose: (POptic s s1, ...POptic sN a) -> POptic s a") or `[...optics]`
 
-`L.compose` performs composition of optics.  The following equations
-characterize composition:
+`L.compose` performs composition of optics and ordinary functions.  The
+following equations characterize composition:
 
 ```jsx
                   L.compose() = L.identity
@@ -513,6 +513,20 @@ L.get(["a", 1], {a: ["b", "c"]})
 // 'c'
 ```
 
+You can also directly compose optics with ordinary functions (with max arity of
+2).  The result of such a composition is a read-only optic.
+
+For example:
+
+```js
+L.get(["x", x => x + 1], {x: 1})
+// 2
+```
+```js
+L.set(["x", x => x + 1], 3, {x: 1})
+// { x: 1 }
+```
+
 Note that [`R.compose`](http://ramdajs.com/docs/#compose) is not the same as
 `L.compose`.
 
@@ -529,9 +543,9 @@ L.compose(optic, L.choose((maybeValue, index) =>
   : toOptic(maybeValue, index)))
 ```
 
-Note that with the [`L.just`](#L-just), `L.chain`, [`L.choice`](#L-choice)
-and [`L.zero`](#L-zero) combinators, one can consider optics as subsuming the
-maybe monad.
+Note that with the [`R.always`](http://ramdajs.com/docs/#always),
+`L.chain`, [`L.choice`](#L-choice) and [`L.zero`](#L-zero) combinators, one can
+consider optics as subsuming the maybe monad.
 
 ##### <a name="L-choice"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-choice) [`L.choice(...lenses) ~> optic`](#L-choice "L.choice: (...PLens s a) -> POptic s a")
 
@@ -1581,7 +1595,9 @@ for [`L.choice`](#L-choice), for example.
 
 #### Read-only mapping
 
-##### <a name="L-just"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-just) [`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")
+##### <a name="L-just"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-just) ~~[`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")~~
+
+**WARNING: `L.just` is obsolete, just use e.g. [`R.always`](http://ramdajs.com/docs/#always).**
 
 `L.just` returns a read-only lens whose view is always the given value.  In
 other words, for all `x`, `y` and `z`:
@@ -1596,7 +1612,9 @@ Note that `L.just(x)` is equivalent to [`L.to(R.always(x))`](#L-to).
 `L.just` can be seen as the unit function of the monad formed
 with [`L.chain`](#L-chain).
 
-##### <a name="L-to"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-to) [`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")
+##### <a name="L-to"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-to) ~~[`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")~~
+
+**WARNING: `L.to` is obsolete, you can directly compose plain functions with optics.**
 
 `L.to` creates a read-only lens whose view is determined by the given function.
 
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index abd3c67..9dce5d7 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -86,7 +86,7 @@ function errorGiven(m, o) {
 }
 
 function reqFunction(o) {
-  if (!(isFunction(o) && o.length === 4))
+  if (!(isFunction(o) && (o.length === 4 || o.length <= 2)))
     errorGiven(expectedOptic, o)
 }
 
@@ -253,7 +253,7 @@ function setU(o, x, s) {
     default:
       if (process.env.NODE_ENV !== "production")
         reqFunction(o)
-      return o(Ident, always(x), s, void 0)
+      return o.length === 4 ? o(Ident, always(x), s, void 0) : s
   }
 }
 
@@ -276,7 +276,7 @@ function getU(l, s) {
     default:
       if (process.env.NODE_ENV !== "production")
         reqFunction(l)
-      return l(Const, id, s, void 0)
+      return l.length === 4 ? l(Const, id, s, void 0) : l(s, void 0)
   }
 }
 
@@ -403,6 +403,9 @@ function partitionIntoIndex(xi2b, xs, ts, fs) {
     (xi2b(x = xs[i], i) ? ts : fs).push(x)
 }
 
+const fromReader = wi2x => (F, xi2yF, w, i) =>
+  (0,F.map)(always(w), xi2yF(wi2x(w, i), i))
+
 //
 
 export function toFunction(o) {
@@ -411,14 +414,14 @@ export function toFunction(o) {
       return funProp(o)
     case "number":
       return funIndex(o)
-    case "function":
-      if (process.env.NODE_ENV !== "production")
-        reqFunction(o)
-      return o
-    default:
+    case "object":
       if (process.env.NODE_ENV !== "production")
         reqArray(o)
       return composed(0, o)
+    default:
+      if (process.env.NODE_ENV !== "production")
+        reqFunction(o)
+      return o.length === 4 ? o : fromReader(o)
   }
 }
 
@@ -430,12 +433,14 @@ export const modify = curry((o, xi2x, s) => {
       return setProp(o, xi2x(getProp(o, s), o), s)
     case "number":
       return setIndex(o, xi2x(getIndex(o, s), o), s)
-    case "function":
+    case "object":
+      return modifyComposed(o, xi2x, s)
+    default:
       if (process.env.NODE_ENV !== "production")
         reqFunction(o)
-      return o(Ident, xi2x, s, void 0)
-    default:
-      return modifyComposed(o, xi2x, s)
+      return o.length === 4
+        ? o(Ident, xi2x, s, void 0)
+        : (xi2x(o(s, void 0), void 0), s)
   }
 })
 
@@ -750,10 +755,21 @@ export const orElse =
 
 // Read-only mapping
 
-export const to = wi2x => (F, xi2yF, w, i) =>
-  (0,F.map)(always(w), xi2yF(wi2x(w, i), i))
+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 just = x => to(always(x))
+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)
+}
 
 // Transforming data
 


Test patch:
diff --git a/test/core.js b/test/core.js
index ee76858..6d461c6 100644
--- a/test/core.js
+++ b/test/core.js
@@ -143,8 +143,8 @@ export const valueOr = v =>
 export const orElse = R.curry((d, l) =>
   choose(x => isDefined(get(l, x)) ? l : d))
 
-export const just = v => to(R.always(v))
-export const to = a2b => lens(a2b, (_, s) => s)
+export const just = R.always
+export const to = R.identity
 
 export const pick = L.pick
 export const replace = R.curry((i, o) =>
diff --git a/test/tests.js b/test/tests.js
index 3744e5e..9d52408 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -259,13 +259,17 @@ describe("L.zero", () => {
 })
 
 describe("L.to", () => {
-  testEq('L.get([0, "x", L.to(R.negate)], [{x:-1}])', 1)
-  testEq('L.set([0, "x", L.to(R.negate)], 2, [{x:-1}])', [{x:-1}])
+  testEq('L.get(L.to(x => x+1), 2)', 3)
+  testEq('L.get(x => x+1, 2)', 3)
+  testEq('L.modify(R.inc, R.negate, 1)', 1)
+  testEq('L.get([0, "x", R.negate], [{x:-1}])', 1)
+  testEq('L.set([0, "x", R.negate], 2, [{x:-1}])', [{x:-1}])
 })
 
 describe("L.just", () => {
   testEq('L.get(L.just("always"), "anything")', "always")
-  testEq('L.set(L.just("always"), "anything", "original")', "original")
+  testEq('L.get(R.always("always"), "anything")', "always")
+  testEq('L.set(R.always("always"), "anything", "original")', "original")
 })
 
 describe("L.chain", () => {
@@ -613,8 +617,6 @@ 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))')
@@ -623,6 +625,8 @@ if (process.env.NODE_ENV !== "production") {
     testThrows('L.set(L.augment({y: () => 1}), 45, {x: 1})')
 
     testThrows('L.set(null, 1, 2)')
+
+    testThrows('L.toFunction((one, too, many) => 1)')
   })
 }
 
diff --git a/test/type.js b/test/type.js
index c7e68ef..de1da00 100644
--- a/test/type.js
+++ b/test/type.js
@@ -61,7 +61,7 @@ export const object = template => object => {
 }
 
 export const fn = (argTys, resultTy) => fn => {
-  if (typeof fn !== "function")
+  if (typeof fn !== "function" || argTys.length < fn.length)
     throw new Error(`fn(${argTys}, ${resultTy}): ${fn}`)
   return R.curryN(argTys.length, function (...argIns) {
     if (argTys.length !== argIns.length)
diff --git a/test/types.js b/test/types.js
index b7758d3..db7ebb9 100644
--- a/test/types.js
+++ b/test/types.js
@@ -26,6 +26,7 @@ const T_opticOf = Category => T.lazy(T_optic => T.or(
   T.integer,
   T.string,
   T.array(T_optic),
+  T.fn([T_maybeData, T_index], T_maybeData),
   T_opticFnOf(Category)))
 
 const T_optic = T_opticOf(T.or(T_applicative, T_functor))
@@ -127,8 +128,8 @@ export const valueOr = T.fn([T.any], T_lens)
 
 export const orElse = T.fn([T_lens, T_lens], T_lens)
 
-export const just = T.fn([T_maybeData], T_lens)
-export const to = T.fn([T.fn([T_maybeData, T_index], T_maybeData)], T_lens)
+export const just = T.fn([T.any], T.fnVar(T.any, T.any))
+export const to = T.fn([T.any], T.any)
 
 export const pick = T.fn([T.props(T_lens)], T_lens)
 export const replace = T.fn([T_maybeData, T_maybeData], T_lens)