Repository: calmm-js/partial.lenses
Pull request #64 (2017-03-17 09:56:39)

Add L.last


Patch:
diff --git a/README.md b/README.md
index ed0c985..4f7bf0b 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,7 @@ parts.  [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html)
       * [`L.find((value, index) => testable) ~> lens`](#L-find "L.find: ((a, Index) -> Boolean) -> PLens [a] a")
       * [`L.findWith(...lenses) ~> lens`](#L-findWith "L.findWith: (PLens s s1, ...PLens sN a) -> PLens [s] a")
       * [`L.index(elemIndex) ~> lens`](#L-index "L.index: Integer -> PLens [a] a") or `elemIndex`
+      * [`L.last ~> lens`](#L-last "L.last: PLens [a] a")
       * [`L.slice(maybeBegin, maybeEnd) ~> lens`](#L-slice "L.slice: Maybe Integer -> Maybe Integer -> PLens [a] [a]")
     * [Lensing objects](#lensing-objects)
       * [`L.prop(propName) ~> lens`](#L-prop "L.prop: (p: a) -> PLens {p: a, ...ps} a") or `propName`
@@ -132,7 +133,6 @@ parts.  [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html)
   * [Benchmarks](#benchmarks)
   * [Lenses all the way](#lenses-all-the-way)
   * [Related work](#related-work)
-* [Contributing](#contributing)
 
 ## Tutorial
 
@@ -1897,6 +1897,35 @@ the handling of `undefined` within partial lenses, this is often not a problem,
 but sometimes you need the "default" value both ways.  In that case you can
 use [`L.define`](#L-define).
 
+##### <a id="L-last"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-last) [`L.last ~> lens`](#L-last "L.last: PLens [a] a")
+
+`L.last` focuses on the last element of an [array-like](#array-like) object or
+works like [`L.append`](#L-append) in case no such element exists.
+
+Focusing on an empty array or `undefined` results in returning `undefined`.  For
+example:
+
+```js
+L.get(L.last, [1,2,3])
+// 3
+```
+```js
+L.get(L.last, [])
+// undefined
+```
+
+Setting value with `L.last` sets the last element of the object or appends the
+value if the focused object is empty or `undefined`.  For example:
+
+```js
+L.set(L.last, 5, [1,2,3])
+// [1,2,5]
+```
+```js
+L.set(L.last, 1, [])
+// [1]
+```
+
 ##### <a id="L-slice"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-slice) [`L.slice(maybeBegin, maybeEnd) ~> lens`](#L-slice "L.slice: Maybe Integer -> Maybe Integer -> PLens [a] [a]")
 
 `L.slice` focuses on a specified range of elements of
@@ -3019,7 +3048,7 @@ transform(R.ifElse(R.allPass([R.is(Object), R.complement(R.is(Array))]),
 //                   including: {the: 'following'} } ] }
 ```
 
-### Related work
+## Related work
 
 Lenses are an old concept and there are dozens of academic papers on lenses and
 dozens of lens libraries for various languages.  Here are just a few links:
@@ -3033,95 +3062,3 @@ dozens of lens libraries for various languages.  Here are just a few links:
 * [Flunc Optics](https://github.com/flunc/optics)
 
 Feel free to suggest more links!
-
-## Contributing
-
-Contributions in the form of pull requests are welcome!
-
-Before starting work on a major PR, it is a good idea to open an issue or maybe
-ask on [gitter](https://gitter.im/calmm-js/chat) whether the contribution sounds
-like something that should be added to this library.
-
-If you allow us to make changes to your PR, it can make the process
-smoother:
-[Allowing changes to a pull request branch created from a fork](https://help.github.com/articles/allowing-changes-to-a-pull-request-branch-created-from-a-fork/).
-We also welcome starting the PR sooner, before it is ready to be merged, rather
-than later so we know what is going on and can help.
-
-Aside from the code changes, a PR should also include tests, and documentation.
-
-When implementing partial optics it is important to consider the behavior of the
-optics when the focus doesn't match the expectation of the optic and also
-whether the optic should propagate removal.  Such behavior should also be
-tested.
-
-It is best not to commit changes to generated files in PRs.  Some of the files
-in `docs`, `lib` and `dist` directories are generated.
-
-### Building
-
-The `prepublish` script is the usual way to build after changes:
-
-```bash
-npm run prepublish
-```
-
-It runs the tests (and lint rules) and builds the `lib` and `dist` files if the
-tests pass.  You can also run the scripts for those subtasks separately.
-
-### Testing
-
-The [tests](./test/tests.js) in this library are written in an atypical manner.
-
-First of all, the tests are written as strings that are `eval`ed.  This way one
-doesn't need to invent names or write prose for tests.
-
-There is also a special test that checks the arity of the exports.  You'll
-notice it if you add an export.
-
-The test code string is then actually used to run more than one test:
-
-* The [`test/core.js`](./test/core.js) file contains a kind of simplified shadow
-  implementation of this library.  The results from the actual library
-  implementation and the shadow implementation are tested to be equal.  The idea
-  is to make the shadow implementations as simple as possible while the library
-  implementations are free to be tweaked and optimized.
-
-* The [`test/types.js`](./test/types.js) file contains contract or type
-  predicates for the library primitives.  Those are also used when running tests
-  to check that the implementation matches the contracts.
-
-When you implement a new combinator, you will need to also add a type contract
-and a shadow implementation for the primitive.
-
-### Documentation
-
-The `docs` folder contains the generated documentation.  You can can open the
-file locally:
-
-```bash
-open docs/index.html
-```
-
-After adding something to the library, you need to run
-
-```bash
-npm run docs-bundle
-```
-
-to update the bundle used by the docs.
-
-To actually build the docs (translate the markdown to html), you can run
-
-```bash
-npm run docs
-```
-
-or you can use the watch
-
-```bash
-npm run docs-watch
-```
-
-which builds the docs if you save `README.md` (you will need to manually refresh
-browser).
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index d7f07af..f84baaf 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -609,6 +609,8 @@ export const firstAs = curry(mkFirst(x => void 0 !== x ? the(x) : x))
 
 export const first = firstAs(id)
 
+export const last = choose(maybeArray => seemsArrayLike(maybeArray) && maybeArray.length ? maybeArray.length-1 : append)
+
 export const foldl = curry((f, r, t, s) =>
   fold(f, r, run(t, Collect, pair, s)))
 


Test patch:
diff --git a/test/core.js b/test/core.js
index e409550..a921b06 100644
--- a/test/core.js
+++ b/test/core.js
@@ -87,6 +87,8 @@ export const firstAs = L.firstAs
 
 export const first = L.firstAs(R.identity)
 
+export const last = L.last
+
 export const foldl = foldx(R.pipe)
 export const foldr = foldx(R.compose)
 
diff --git a/test/tests.js b/test/tests.js
index 40cd643..6f4a1e3 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -177,6 +177,7 @@ describe("arities", () => {
     inverse: 1,
     iso: 2,
     just: 1,
+    last: 4,
     lazy: 1,
     lens: 2,
     log: 0,
@@ -819,3 +820,12 @@ describe("lazy folds", () => {
   testEq(`L.and(L.elems, [])`, true)
   testEq(`L.or(L.elems, [])`, false)
 })
+
+describe("L.last", () => {
+  testEq(`L.get(L.last, undefined)`, undefined)
+  testEq(`L.get(L.last, [])`, undefined)
+  testEq(`L.get(L.last, [5])`, 5)
+  testEq(`L.set(L.last, 5, undefined)`, [5])
+  testEq(`L.set(L.last, 5, [])`, [5])
+  testEq(`L.set(L.last, 5, [1,2])`, [1,5])
+})
\ No newline at end of file
diff --git a/test/types.js b/test/types.js
index 272b1ad..3e1832f 100644
--- a/test/types.js
+++ b/test/types.js
@@ -131,6 +131,8 @@ export const firstAs =
        T.any)
 export const first = T.fn([T_traversal, T_maybeData], T.any)
 
+export const last = T_lens
+
 export const foldl =
   T.fn([T.fn([T.any, T_maybeData, T_index], T.any),
         T.any,