From 14fd94cc2301bd3023030a04927c4022b36f3164 Mon Sep 17 00:00:00 2001 From: Vesa Karvonen Date: Mon, 13 Aug 2018 15:35:45 +0300 Subject: [PATCH] Switched `L.get` from `Constant` functor to `Select` applicative --- CHANGELOG.md | 91 ++++++- README.md | 561 ++++++++++++++++++++++++------------------ bench/bench.js | 6 +- src/partial.lenses.js | 322 +++++++++++------------- test/tests.js | 115 +++++---- test/types.js | 18 +- 6 files changed, 626 insertions(+), 487 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbda0297..358e7ace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,89 @@ # Partial Lenses Changelog +## 14.0.0 + +*The current plan is to change Partial Lenses to support so called naked or +prototypeless objects with `null` prototype (i.e. `Object.create(null)`) in a +following major version. This is a breaking change although it is likely that +it will not affect most users. Usefully warning for this change of behaviour by +adding diagnostics to optics seems somewhat difficult.* + +Previously obsoleted `L.iftes` was removed. + +The `L.Constant` functor was removed. It can be replaced as follows: + +```diff +-L.constant ++{map: (_, x) => x} +``` + +`L.get` has been changed to use the now exported `L.Select` applicative instead +of the removed `L.Constant` functor. The reason for this change is that it both +generalizes `L.get` and simplifies things overall. + +`L.get` now works exactly like `L.select`. `L.select` and `L.selectAs` have +been consequently obsoleted. Change usages as follows: + +```diff +-L.select(...) ++L.get(...) +``` + +```diff +-L.selectAs(...) ++L.getAs(...) +``` + +In the cases where `L.get` previously returned a valid result, the only +differences are in the cases where `L.zero` is involved. `L.zero` is used by +several other combinators that could be used as lenses including the +conditionals, + +* `L.cond`, and +* `L.condOf`, + +and the querying combinators, + +* `L.chain`, +* `L.choice`, +* `L.optional`, +* `L.unless`, and +* `L.when`, + +and the transform ops, + +* `L.assignOp`, +* `L.modifyOp`, +* `L.removeOp`, and +* `L.setOp`. + +Previously `L.zero` was implemented so that it did something reasonable even +with a plain functor. Since no operation in this library now uses a plain +functor, the special case behaviour of `L.zero` has been removed. When +previously used with `L.get`, `L.zero` passed `undefined` to the inner optics +and ignored what the inner optics returned. For example, previously: + +```js +L.get(['x', L.when(x => x > 0), L.valueOr(0)], {x: -1}) +// 0 +``` + +but now `L.zero` exits early: + +```js +L.get(['x', L.when(x => x > 0), L.valueOr(0)], {x: -1}) +// undefined +``` + +This is clearly a breaking change. However, this is unlikely to affect a large +number of use cases. To get the old behavior, use of `L.zero` need to be +avoided. In the example case, one could write: + +```js +L.get(['x', L.ifElse(x => x > 0, [], R.always(0))], {x: -1}) +// 0 +``` + ## 13.10.0 There is no longer guarantee that optic operations return newly allocated data @@ -69,13 +153,6 @@ with `L.valueOr`. Removed previously obsoleted `L.findHint`. -The current plan is to change Partial Lenses to support so called naked or -prototypeless objects with `null` prototype (i.e. `Object.create(null)`) in the -next major version. This is a breaking change although it is likely that it -will not affect most users. Usefully warning for this change of behaviour by -adding diagnostics to optics seems somewhat difficult. *This note was moved -from 12.0.0.* - ## 12.0.0 As documented in 11.21.0: diff --git a/README.md b/README.md index 47deb50b..dc7c4526 100644 --- a/README.md +++ b/README.md @@ -63,17 +63,9 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [`L.choices(optic, ...optics) ~> optic`](#L-choices "L.choices: (POptic s a, ...POptic s a) -> POptic s a") v11.10.0 * [`L.choose((maybeValue, index) => optic) ~> optic`](#L-choose "L.choose: ((Maybe s, Index) -> POptic s a) -> POptic s a") v1.0.0 * L.cond(...[(maybeValue, index) => testable, consequentOptic][, [alternativeOptic]]) ~> optic v13.1.0 - * L.condOf(lens, ...[(maybeValue, index) => testable, consequentOptic][, [alternativeOptic]]) ~> optic v13.5.0 + * L.condOf(traversal, ...[(maybeValue, index) => testable, consequentOptic][, [alternativeOptic]]) ~> optic v13.5.0 * [`L.ifElse((maybeValue, index) => testable, optic, optic) ~> optic`](#L-ifElse "L.ifElse: ((Maybe s, Index) -> Boolean) -> POptic s a -> POptic s a -> POptic s a") v13.1.0 - * ~~[`L.iftes((maybeValue, index) => testable, consequentOptic, ...[, alternativeOptic]) ~> optic`](#L-iftes "L.iftes: ((Maybe s, Index) -> Boolean) -> PLens s a -> PLens s a -> PLens s a") v11.14.0~~ * [`L.orElse(backupOptic, primaryOptic) ~> optic`](#L-orElse "L.orElse: (POptic s a, POptic s a) -> POptic s a") v2.1.0 - * [Querying](#querying) - * [`L.chain((value, index) => optic, optic) ~> optic`](#L-chain "L.chain: ((a, Index) -> POptic s b) -> POptic s a -> POptic s b") v3.1.0 - * [`L.choice(...optics) ~> optic`](#L-choice "L.choice: (...POptic s a) -> POptic s a") v2.1.0 - * [`L.optional ~> optic`](#L-optional "L.optional: POptic a a") v3.7.0 - * [`L.unless((maybeValue, index) => testable) ~> optic`](#L-unless "L.unless: ((Maybe a, Index) -> Boolean) -> POptic a a") v12.1.0 - * [`L.when((maybeValue, index) => testable) ~> optic`](#L-when "L.when: ((Maybe a, Index) -> Boolean) -> POptic a a") v5.2.0 - * [`L.zero ~> optic`](#L-zero "L.zero: POptic s a") v6.0.0 * [Indices](#indices) * [`L.joinIx(optic) ~> optic`](#L-joinIx "L.joinIx: POptic s a -> POptic s a") v13.15.0 * [`L.mapIx((index, maybeValue) => index) ~> optic`](#L-mapIx "L.mapIx: ((Index, Maybe a) -> Index) -> POptic a a") v13.15.0 @@ -84,9 +76,9 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [`L.getLog(lens, maybeData) ~> maybeValue`](#L-getLog "L.getLog: PLens s a -> Maybe s -> Maybe a") v13.14.0 * [`L.log(...labels) ~> optic`](#L-log "L.log: (...Any) -> POptic s s") v3.2.0 * [Internals](#internals) - * [`L.Constant ~> Functor`](#L-Constant "L.Constant: Functor") v13.7.0 * [`L.Identity ~> Monad`](#L-Identity "L.Identity: Monad") v13.7.0 * [`L.IdentityAsync ~> Monadish`](#L-IdentityAsync "L.IdentityAsync: Monadish") v13.12.0 + * [`L.Select ~> Applicative`](#L-Select "L.Select: Applicative") v14.0.0 * [`L.toFunction(optic) ~> optic`](#L-toFunction "L.toFunction: POptic s t a b -> (Maybe s, Index, (Functor|Applicative|Monad) c, (Maybe a, Index) -> c b) -> c t") v7.0.0 * [Transforms](#transforms) * [Operations on transforms](#operations-on-transforms) @@ -95,10 +87,10 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [Sequencing](#sequencing) * [`L.seq(...transforms) ~> transform`](#L-seq "L.seq: (...PTransform s a) -> PTransform s a") v9.4.0 * [Transforming](#transforming) - * [`L.assignOp(object) ~> optic`](#L-assignOp "L.assignOp: {p1: a1, ...ps} -> POptic {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v11.13.0 - * [`L.modifyOp((maybeValue, index) => maybeValue) ~> optic`](#L-modifyOp "L.modifyOp: ((Maybe a, Index) -> Maybe a) -> POptic a a") v11.7.0 - * [`L.removeOp ~> optic`](#L-removeOp "L.removeOp: POptic a a") v11.7.0 - * [`L.setOp(maybeValue) ~> optic`](#L-setOp "L.setOp: Maybe a -> POptic a a") v11.7.0 + * [`L.assignOp(object) ~> traversal`](#L-assignOp "L.assignOp: {p1: a1, ...ps} -> PTraversal {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v11.13.0 + * [`L.modifyOp((maybeValue, index) => maybeValue) ~> traversal`](#L-modifyOp "L.modifyOp: ((Maybe a, Index) -> Maybe a) -> PTraversal a a") v11.7.0 + * [`L.removeOp ~> traversal`](#L-removeOp "L.removeOp: PTraversal a a") v11.7.0 + * [`L.setOp(maybeValue) ~> traversal`](#L-setOp "L.setOp: Maybe a -> PTraversal a a") v11.7.0 * [Traversals](#traversals) * [Creating new traversals](#creating-new-traversals) * [`L.branch({prop: traversal, ...props}) ~> traversal`](#L-branch "L.branch: {p1: PTraversal p1 a, ...pts} -> PTraversal {p1: p1, ...ps} a") v5.1.0 @@ -116,6 +108,13 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [`L.query(...traversals) ~> traversal`](#L-query "L.query: (PTraversal s1 s2, ...PTraversal sN a) ~> PTraversal JSON a") v13.6.0 * [`L.satisfying((maybeValue, index) => testable) ~> traversal`](#L-satisfying "L.satisfying: ((Maybe s, Index) -> Boolean) -> PTraversal JSON a") v13.3.0 * [`L.values ~> traversal`](#L-values "L.values: PTraversal {p: a, ...ps} a") v7.3.0 + * [Querying](#querying) + * [`L.chain((value, index) => optic, optic) ~> traversal`](#L-chain "L.chain: ((a, Index) -> POptic s b) -> POptic s a -> PTraversal s b") v3.1.0 + * [`L.choice(...optics) ~> traversal`](#L-choice "L.choice: (...POptic s a) -> PTraversal s a") v2.1.0 + * [`L.optional ~> traversal`](#L-optional "L.optional: PTraversal a a") v3.7.0 + * [`L.unless((maybeValue, index) => testable) ~> traversal`](#L-unless "L.unless: ((Maybe a, Index) -> Boolean) -> PTraversal a a") v12.1.0 + * [`L.when((maybeValue, index) => testable) ~> traversal`](#L-when "L.when: ((Maybe a, Index) -> Boolean) -> PTraversal a a") v5.2.0 + * [`L.zero ~> traversal`](#L-zero "L.zero: PTraversal s a") v6.0.0 * [Folds over traversals](#folds-over-traversals) * [`L.all((maybeValue, index) => testable, traversal, maybeData) ~> boolean`](#L-all "L.all: ((Maybe a, Index) -> Boolean) -> PTraversal s a -> Boolean") v9.6.0 * [`L.and(traversal, maybeData) ~> boolean`](#L-and "L.and: PTraversal s Boolean -> Boolean") v9.6.0 @@ -132,6 +131,8 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [`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") v7.2.0 * [`L.forEach((maybeValue, index) => undefined, traversal, maybeData) ~> undefined`](#L-forEach "L.forEach: ((Maybe a, Index) -> Undefined) -> PTraversal s a -> Maybe s -> Undefined") v11.20.0 * [`L.forEachWith(() => context, (context, maybeValue, index) => undefined, traversal, maybeData) ~> context`](#L-forEachWith "L.forEachWith: (() -> c) -> ((c, Maybe a, Index) -> Undefined) -> PTraversal s a -> Maybe s -> c") v13.4.0 + * [`L.get(traversal, maybeData) ~> maybeValue`](#L-get "L.get: PTraversal s a -> Maybe s -> Maybe a") v2.2.0 + * [`L.getAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> maybeValue`](#L-getAs "L.getAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> Maybe b") v14.0.0 * [`L.isDefined(traversal, maybeData) ~> boolean`](#L-isDefined "L.isDefined: PTraversal s a -> Maybe s -> Boolean") v11.8.0 * [`L.isEmpty(traversal, maybeData) ~> boolean`](#L-isEmpty "L.isEmpty: PTraversal s a -> Maybe s -> Boolean") v11.5.0 * [`L.join(string, traversal, maybeData) ~> string`](#L-join "L.join: String -> PTraversal s a -> Maybe s -> String") v11.2.0 @@ -146,13 +147,11 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [`L.or(traversal, maybeData) ~> boolean`](#L-or "L.or: PTraversal s Boolean -> Boolean") v9.6.0 * [`L.product(traversal, maybeData) ~> number`](#L-product "L.product: PTraversal s Number -> Maybe s -> Number") v7.2.0 * [`L.productAs((maybeValue, index) => number, traversal, maybeData) ~> number`](#L-productAs "L.productAs: ((Maybe a, Index) -> Number) -> PTraversal s a -> Maybe s -> Number") v11.2.0 - * [`L.select(traversal, maybeData) ~> maybeValue`](#L-select "L.select: PTraversal s a -> Maybe s -> Maybe a") v9.8.0 - * [`L.selectAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> maybeValue`](#L-selectAs "L.selectAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> Maybe b") v9.8.0 + * ~~[`L.select(traversal, maybeData) ~> maybeValue`](#L-select "L.select: PTraversal s a -> Maybe s -> Maybe a") v9.8.0~~ + * ~~[`L.selectAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> maybeValue`](#L-selectAs "L.selectAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> Maybe b") v9.8.0~~ * [`L.sum(traversal, maybeData) ~> number`](#L-sum "L.sum: PTraversal s Number -> Maybe s -> Number") v7.2.0 * [`L.sumAs((maybeValue, index) => number, traversal, maybeData) ~> number`](#L-sumAs "L.sumAs: ((Maybe a, Index) -> Number) -> PTraversal s a -> Maybe s -> Number") v11.2.0 * [Lenses](#lenses) - * [Operations on lenses](#operations-on-lenses) - * [`L.get(lens, maybeData) ~> maybeValue`](#L-get "L.get: PLens s a -> Maybe s -> Maybe a") v2.2.0 * [Creating new lenses](#creating-new-lenses) * [`L.lens((maybeData, index) => maybeValue, (maybeValue, maybeData, index) => maybeData) ~> lens`](#L-lens "L.lens: ((Maybe s, Index) -> Maybe a) -> ((Maybe a, Maybe s, Index) -> Maybe s) -> PLens s a") v1.0.0 * [`L.getter((maybeData, index) => maybeValue) ~> lens`](#L-getter "L.getter: ((Maybe s, Index) -> Maybe a) -> PLens s a") v13.16.0 @@ -238,7 +237,7 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [`List` indexing](#list-indexing) * [Interfacing traversals](#interfacing-traversals) * [Deepening topics](#deepening-topics) - * [Understanding `L.filter`, `L.find`, `L.select`, and `L.when`](#understanding-filter-find-select-and-when) + * [Understanding `L.filter`, `L.find`, `L.get`, and `L.when`](#understanding-filter-find-get-and-when) * [Advanced topics](#advanced-topics) * [Performance tips](#performance-tips) * [Nesting traversals does not create intermediate aggregates](#nesting-traversals-does-not-create-intermediate-aggregates) @@ -446,7 +445,8 @@ L.get(textIn('fi'), undefined) // '' ``` -With partial lenses, `undefined` is the equivalent of non-existent. +With partial lenses, [`undefined` is the equivalent of +non-existent](#use-of-undefined). #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#updating-data) [Updating data](#updating-data) @@ -804,12 +804,12 @@ no longer the same operation—the special value is not the first element of the array. Now, in partial lenses, the idea is that in case the input does not match the -expectation of an optic, then the input is treated as being `undefined`, which -is the equivalent of non-existent: reading through the optic gives `undefined` -and writing through the optic replaces the focus with the written value. This -makes the optics in this library partial and allows specific partial optics, -such as the simple [`L.prop`](#L-prop) lens, to be used in a wider range of -situations than corresponding total optics. +expectation of an optic, then the [input is treated as being `undefined`, which +is the equivalent of non-existent](#use-of-undefined): reading through the optic +gives `undefined` and writing through the optic replaces the focus with the +written value. This makes the optics in this library partial and allows +specific partial optics, such as the simple [`L.prop`](#L-prop) lens, to be used +in a wider range of situations than corresponding total optics. Making all optics partial has a number of consequences. For one thing, it can potentially hide bugs: an incorrectly specified optic treats the input as @@ -931,7 +931,7 @@ behave. Here is a table of the means of composition supported by this library: | [Nesting](#nesting) | [`L.compose(...optics)`](#L-compose) or `[...optics]` | [Monoid](https://en.wikipedia.org/wiki/Monoid) over [unityped](http://cs.stackexchange.com/questions/18847/if-dynamically-typed-languages-are-truly-statically-typed-unityped-languages-w) [optics](#optics) | [Recursing](#recursing) | [`L.lazy(optic => optic)`](#L-lazy) | [Fixed point](https://en.wikipedia.org/wiki/Fixed-point_combinator) | [Adapting](#adapting) | [`L.choices(optic, ...optics)`](#L-choices) | [Semigroup](https://en.wikipedia.org/wiki/Semigroup) over [optics](#optics) -| [Querying](#querying) | [`L.choice(...optics)`](#L-choice) and [`L.chain(value => optic, optic)`](#L-chain) | [MonadPlus](https://en.wikibooks.org/wiki/Haskell/Alternative_and_MonadPlus) over [optics](#optics) +| [Querying](#querying) | [`L.choice(...optics)`](#L-choice) and [`L.chain(value => optic, optic)`](#L-chain) | [MonadPlus](https://en.wikibooks.org/wiki/Haskell/Alternative_and_MonadPlus) over [traversals](#traversals) | Picking | [`L.pick({...prop:lens})`](#L-pick) | Product of [lenses](#lenses) | Branching | [`L.branch({...prop:traversal})`](#L-branch) | [Coproduct](https://en.wikipedia.org/wiki/Coproduct) of [traversals](#traversals) | [Sequencing](#sequencing) | [`L.seq(...transforms)`](#L-seq) | Monad over [transforms](#transforms) @@ -1394,15 +1394,33 @@ L.cond(..., [R.T, alternative]) because in the latter case `L.cond` cannot determine that a user defined predicate will always be true and has to construct a more expensive optic. +Note that when no `[alternative]` is specified, `L.cond` returns a +[traversal](#traversals), because the default [`L.zero`](#L-zero) is a +traversal. + Note that `L.cond` can be implemented using [`L.choose`](#L-choose), but not vice versa. [`L.choose`](#L-choose) not only allows the optic to be chosen dynamically, but also allows the optic to be constructed dynamically and using the data at the focus. -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-condOf) L.condOf(lens, ...[(maybeValue, index) => testable, consequentOptic][, [alternativeOptic]]) ~> optic v13.5.0 +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-condOf) L.condOf(traversal, ...[(maybeValue, index) => testable, consequentOptic][, [alternativeOptic]]) ~> optic v13.5.0 `L.condOf` is like [`L.cond`](#L-cond) except the first argument to `L.condOf` -is a lens to get the parameters for the predicates from the underlying view. +is a traversal whose focuses are tested with the predicates. + +```jsx +L.condOf(traversal, + [ predicate, consequent ] + , ... + [ , [ alternative ] ] ) +``` + +`L.condOf` acts like the *consequent* optic of first `[predicate, consequent]` +pair whose *predicate* accepts [any](#L-any) focus produced by the traversal. +The last argument to `L.condOf` can be an `[alternative]` singleton, where the +*alternative* is an optic to be used in case none of the predicates accepts +[any](#L-any) focus produced by the traversal. If there is no `[alternative]` +[`L.zero`](#L-zero) is used. For example: @@ -1418,6 +1436,14 @@ L.get( // 'Try writing this with `L.cond`.' ``` +Note that `L.condOf(t, [p1, o1], ..., [pN, oN], [o])` is roughly equivalent to a +combination of [`L.any`](#L-any) and [`L.cond`](#L-cond): `L.cond([L.any(p1, t), +o1], ..., [L.any(pN, t), oN], [o])`. + +Note that when no `[alternative]` is specified, `L.condOf` returns a +[traversal](#traversals), because the default [`L.zero`](#L-zero) is a +traversal. + ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-ifElse) [`L.ifElse((maybeValue, index) => testable, optic, optic) ~> optic`](#L-ifElse "L.ifElse: ((Maybe s, Index) -> Boolean) -> POptic s a -> POptic s a -> POptic s a") v13.1.0 `L.ifElse` creates an optic whose operation is selected based on the given @@ -1437,45 +1463,6 @@ L.modify(L.ifElse(Array.isArray, L.elems, L.values), R.inc, {x: 1, y: 2, z: 3}) // { x: 2, y: 3, z: 4 } ``` -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-iftes) ~~[`L.iftes((maybeValue, index) => testable, consequentOptic, ...[, alternativeOptic]) ~> optic`](#L-iftes "L.iftes: ((Maybe s, Index) -> Boolean) -> PLens s a -> PLens s a -> PLens s a") v11.14.0~~ - -**WARNING: `L.iftes` has been obsoleted. Use [`L.ifElse`](#L-ifElse) or -[`L.cond`](#L-cond) instead. See [CHANGELOG](./CHANGELOG.md#1310) for -details.** - -`L.iftes` creates an optic whose operation is selected from the given optics and -predicates on the underlying view. - -```jsx -L.iftes( predicate, consequent - [ , ... ] - [ , alternative ] ) -``` - -`L.iftes` is not curried unlike most functions in this library. `L.iftes` -requires at least two arguments and successive arguments form *predicate* - -*consequent* pairs. The predicates are functions on the underlying view and are -tested sequentially. The consequents are optics and `L.iftes` acts like the -consequent corresponding to the first predicate that returns true. If `L.iftes` -is given an odd number of arguments, the last argument is the *alternative* -taken in case none of the predicates returns true. If all predicates return -false and there is no alternative, `L.iftes` acts like [`L.zero`](#L-zero). - -For example: - -```js -const minorAxis = L.iftes(({x, y} = {}) => Math.abs(y) < Math.abs(x), 'y', 'x') - -L.get(minorAxis, {x: -3, y: 1}) -// 1 -``` -```js -L.modify(minorAxis, R.negate, {x: -3, y: 1}) -// { x: -3, y: -1 } -``` - -Note that `L.iftes` can be implemented using [`L.choose`](#L-choose). - ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-orElse) [`L.orElse(backupOptic, primaryOptic) ~> optic`](#L-orElse "L.orElse: (POptic s a, POptic s a) -> POptic s a") v2.1.0 `L.orElse(backupOptic, primaryOptic)` acts like `primaryOptic` when its view is @@ -1485,115 +1472,6 @@ Note that [`L.choice(...optics)`](#L-choice) is equivalent to `optics.reduceRight(L.orElse, L.zero)` and [`L.choices(...optics)`](#L-choices) is equivalent to `optics.reduce(L.orElse)`. -#### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#querying) [Querying](#querying) - -Querying combinators allow one to use optics to query data structures. Querying -is distinguished from [adapting](#adapting) in that querying defaults to an -empty or read-only [zero](#L-zero). - -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-chain) [`L.chain((value, index) => optic, optic) ~> optic`](#L-chain "L.chain: ((a, Index) -> POptic s b) -> POptic s a -> POptic s b") v3.1.0 - -`L.chain` provides a monadic -[chain](https://github.com/rpominov/static-land/blob/master/docs/spec.md#chain) -combinator for querying with optics. `L.chain(toOptic, optic)` is equivalent to - -```jsx -L.compose( - optic, - L.choose( - (value, index) => value === undefined ? L.zero : toOptic(value, index) - ) -) -``` - -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. - -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-choice) [`L.choice(...optics) ~> optic`](#L-choice "L.choice: (...POptic s a) -> POptic s a") v2.1.0 - -`L.choice` returns a partial optic that acts like the first of the given optics -whose view is not `undefined` on the given data structure. When the views of -all of the given optics are `undefined`, the returned optic acts like -[`L.zero`](#L-zero), which is the identity element of `L.choice`. See also -[`L.choices`](#L-choices). - -For example: - -```js -L.modify([L.elems, L.choice('a', 'd')], R.inc, [{R: 1}, {a: 1}, {d: 2}]) -// [ { R: 1 }, { a: 2 }, { d: 3 } ] -``` - -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-optional) [`L.optional ~> optic`](#L-optional "L.optional: POptic a a") v3.7.0 - -`L.optional` is an optic over an optional element. When used as a traversal, -and the focus is `undefined`, the traversal is empty. When used as a lens, and -the focus is `undefined`, the lens will be read-only. - -As an example, consider the difference between: - -```js -L.set([L.elems, 'x'], 3, [{x: 1}, {y: 2}]) -// [ { x: 3 }, { y: 2, x: 3 } ] -``` - -and: - -```js -L.set([L.elems, 'x', L.optional], 3, [{x: 1}, {y: 2}]) -// [ { x: 3 }, { y: 2 } ] -``` - -Note that `L.optional` is equivalent to [`L.when(x => x !== -undefined)`](#L-when). - -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-unless) [`L.unless((maybeValue, index) => testable) ~> optic`](#L-unless "L.unless: ((Maybe a, Index) -> Boolean) -> POptic a a") v12.1.0 - -`L.unless` allows one to selectively skip elements within a traversal or to -selectively turn a lens into a read-only lens whose view is `undefined`. See -also [`L.when`](#L-when). - -For example: - -```js -L.modify([L.elems, L.unless(x => x < 0)], R.negate, [0, -1, 2, -3, 4]) -// [ -0, -1, -2, -3, -4 ] -``` - -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-when) [`L.when((maybeValue, index) => testable) ~> optic`](#L-when "L.when: ((Maybe a, Index) -> Boolean) -> POptic a a") v5.2.0 - -`L.when` allows one to selectively skip elements within a traversal or to -selectively turn a lens into a read-only lens whose view is `undefined`. See -also [`L.unless`](#L-unless). - -For example: - -```js -L.modify([L.elems, L.when(x => x > 0)], R.negate, [0, -1, 2, -3, 4]) -// [ 0, -1, -2, -3, -4 ] -``` - -Note that `L.when(p)` is equivalent to [`L.choose((x, i) => p(x, i) ? -L.identity : L.zero)`](#L-choose). - -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-zero) [`L.zero ~> optic`](#L-zero "L.zero: POptic s a") v6.0.0 - -`L.zero` is the identity element of [`L.choice`](#L-choice) and -[`L.chain`](#L-chain). As a traversal, `L.zero` is a traversal of no elements -and as a lens, i.e. when used with [`L.get`](#L-get), `L.zero` is a read-only -lens whose view is always `undefined`. - -For example: - -```js -L.collect( - [L.elems, L.cond([R.is(Array), L.elems], [R.is(Object), 'x'], [L.zero])], - [1, {x: 2}, [3, 4]] -) -// [ 2, 3, 4 ] -``` - #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#indices) [Indices](#indices) The indexing combinators allow one to manipulate the indices passed down by @@ -1709,9 +1587,9 @@ returned. `L.getLog`, like [`L.log`](#L-log), is intended for debugging. For example: ```js -L.getLog(['x', 0, 'y'], {x: [{y: 101}]}) -// { x: [ { y: 101 } ] } <= [ { y: 101 } ] <= { y: 101 } <= 101 -// 101 +L.getLog(['data', L.elems, 'y'], {data: [{x: 1}, {y: 2}]}) +// { data: [ { x: 1 }, { y: 2 } ] } <= [ { x: 1 }, { y: 2 } ] <= { y: 2 } <= 2 +// 2 ``` (If you are looking at the above snippet in the interactive version of this @@ -1747,14 +1625,6 @@ L.set(['x', L.log('%s x: %j')], '11', {x: 10}) #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#internals) [Internals](#internals) -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-Constant) [`L.Constant ~> Functor`](#L-Constant "L.Constant: Functor") v13.7.0 - -`L.Constant` is the [Static -Land](https://github.com/rpominov/static-land/blob/master/docs/spec.md) -compatible constant -[`Functor`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#functor) -definition used by Partial Lenses. - ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-Identity) [`L.Identity ~> Monad`](#L-Identity "L.Identity: Monad") v13.7.0 `L.Identity` is the [Static @@ -1772,6 +1642,42 @@ monad](https://buzzdecafe.github.io/2018/04/10/no-promises-are-not-monads), which explains the "monadish". Fortunately one usually does not want nested promises in which case the approximation can be close enough. +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-Constant) [`L.Select ~> Applicative`](#L-Select "L.Select: Applicative") v14.0.0 + +`L.Select` is the [Static +Land](https://github.com/rpominov/static-land/blob/master/docs/spec.md) +compatible +[`Applicative`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#applicative) +definition that extends the constant functor to select the first non-`undefined` +element. + +The basis for `Select` is the following +[monoid](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monoid) +over JavaScript values: + +```js +const Defined = { + empty: _ => undefined, + concat: (l, r) => l !== undefined ? l : r +} +``` + +It is a monoid, because it satisfies the Monoid laws: + +```js +const MonoidLaws = (M, x, y, z) => ({ + associativity: test(M.concat(M.concat(x, y), z), M.concat(x, M.concat(y, z))), + leftIdentity: test(M.concat(M.empty(), x), x) , + rightIdentity: test(M.concat(x, M.empty()), x) +}) + +MonoidLaws(Defined, {Try: 'any'}, 'JavaScript', ['values']) +// {associativity: true, leftIdentity: true, rightIdentity: true} +``` + +In Partial Lenses [`undefined` is used to representing +nothingness](#use-of-undefined). + ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-toFunction) [`L.toFunction(optic) ~> optic`](#L-toFunction "L.toFunction: POptic s t a b -> (Maybe s, Index, (Functor|Applicative|Monad) c, (Maybe a, Index) -> c b) -> c t") v7.0.0 `L.toFunction` converts a given optic, which can be a [string](#L-prop), an @@ -1953,10 +1859,12 @@ which is not the same as the [querying monad](#L-chain). #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#transforming) [Transforming](#transforming) -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-assignOp) [`L.assignOp(object) ~> optic`](#L-assignOp "L.assignOp: {p1: a1, ...ps} -> POptic {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v11.13.0 +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-assignOp) [`L.assignOp(object) ~> traversal`](#L-assignOp "L.assignOp: {p1: a1, ...ps} -> PTraversal {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v11.13.0 -`L.assignOp` creates an optic that merges the given object into the object in -focus. +`L.assignOp` creates a transform that merges the given object into the object in +focus. When used as a traversal, `L.assignOp` acts as a traversal of no +elements. Usually, however, `L.assignOp` is used within +[transforms](#transforms). For example: @@ -1965,11 +1873,10 @@ L.transform([L.elems, L.assignOp({y: 1})], [{x: 3}, {x: 4, y: 5}]) // [ { x: 3, y: 1 }, { x: 4, y: 1 } ] ``` -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-modifyOp) [`L.modifyOp((maybeValue, index) => maybeValue) ~> optic`](#L-modifyOp "L.modifyOp: ((Maybe a, Index) -> Maybe a) -> POptic a a") v11.7.0 +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-modifyOp) [`L.modifyOp((maybeValue, index) => maybeValue) ~> traversal`](#L-modifyOp "L.modifyOp: ((Maybe a, Index) -> Maybe a) -> PTraversal a a") v11.7.0 -`L.modifyOp` creates an optic that maps the focus with the given function. When -used as a traversal, `L.modifyOp` acts as a traversal of no elements. When used -as a lens, `L.modifyOp` acts as a read-only lens whose view is the mapped focus. +`L.modifyOp` creates a transform that maps the focus with the given function. +When used as a traversal, `L.modifyOp` acts as a traversal of no elements. Usually, however, `L.modifyOp` is used within [transforms](#transforms). For example: @@ -1987,7 +1894,7 @@ L.transform( // ys: [ 0, 1, 2 ] } ``` -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-removeOp) [`L.removeOp ~> optic`](#L-removeOp "L.removeOp: POptic a a") v11.7.0 +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-removeOp) [`L.removeOp ~> traversal`](#L-removeOp "L.removeOp: PTraversal a a") v11.7.0 `L.removeOp` is shorthand for [`L.setOp(undefined)`](#L-setOp). @@ -2020,7 +1927,7 @@ L.transform( The idea is to filter the data both by `time` and by `subelements`. -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-setOp) [`L.setOp(maybeValue) ~> optic`](#L-setOp "L.setOp: Maybe a -> POptic a a") v11.7.0 +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-setOp) [`L.setOp(maybeValue) ~> traversal`](#L-setOp "L.setOp: Maybe a -> PTraversal a a") v11.7.0 `L.setOp(x)` is shorthand for [`L.modifyOp(R.always(x))`](#L-modifyOp). @@ -2270,10 +2177,10 @@ L.modify( // { language: 'sv', text: 'Rubrik' } ] } ``` -And one can also view the text of a specific language using [select](#L-select): +And one can also view the text of a specific language: ```js -L.select(L.query(L.when(R.propEq('language', 'sv')), 'text'), sampleTitles) +L.get(L.query(L.when(R.propEq('language', 'sv')), 'text'), sampleTitles) // 'Rubrik' ``` @@ -2332,6 +2239,111 @@ L.modify([L.rewrite(objectTo(XYZ)), L.values], R.negate, new XYZ(1, 2, 3)) Note that `L.values` is equivalent to [`L.branchOr([], {})`](#L-branchOr). +#### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#querying) [Querying](#querying) + +Querying combinators allow one to use optics to query data structures. Querying +is distinguished from [adapting](#adapting) in that querying defaults to an +empty or read-only [zero](#L-zero). + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-chain) [`L.chain((value, index) => optic, optic) ~> traversal`](#L-chain "L.chain: ((a, Index) -> POptic s b) -> POptic s a -> PTraversal s b") v3.1.0 + +`L.chain` provides a monadic +[chain](https://github.com/rpominov/static-land/blob/master/docs/spec.md#chain) +combinator for querying with optics. `L.chain(toOptic, optic)` is equivalent to + +```jsx +L.compose( + optic, + L.choose( + (value, index) => value === undefined ? L.zero : toOptic(value, index) + ) +) +``` + +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. + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-choice) [`L.choice(...optics) ~> traversal`](#L-choice "L.choice: (...POptic s a) -> PTraversal s a") v2.1.0 + +`L.choice` returns a partial optic that acts like the first of the given optics +whose view is not `undefined` on the given data structure. When the views of +all of the given optics are `undefined`, the returned optic acts like +[`L.zero`](#L-zero), which is the identity element of `L.choice`. See also +[`L.choices`](#L-choices). + +For example: + +```js +L.modify([L.elems, L.choice('a', 'd')], R.inc, [{R: 1}, {a: 1}, {d: 2}]) +// [ { R: 1 }, { a: 2 }, { d: 3 } ] +``` + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-optional) [`L.optional ~> traversal`](#L-optional "L.optional: PTraversal a a") v3.7.0 + +`L.optional` is an optic over an optional element. When used as a traversal, +and the focus is `undefined`, the traversal is empty. When used as a lens, and +the focus is `undefined`, the lens will be read-only. + +As an example, consider the difference between: + +```js +L.set([L.elems, 'x'], 3, [{x: 1}, {y: 2}]) +// [ { x: 3 }, { y: 2, x: 3 } ] +``` + +and: + +```js +L.set([L.elems, 'x', L.optional], 3, [{x: 1}, {y: 2}]) +// [ { x: 3 }, { y: 2 } ] +``` + +Note that `L.optional` is equivalent to [`L.when(x => x !== +undefined)`](#L-when). + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-unless) [`L.unless((maybeValue, index) => testable) ~> traversal`](#L-unless "L.unless: ((Maybe a, Index) -> Boolean) -> PTraversal a a") v12.1.0 + +`L.unless` allows one to selectively skip elements within a traversal. See also +[`L.when`](#L-when). + +For example: + +```js +L.modify([L.elems, L.unless(x => x < 0)], R.negate, [0, -1, 2, -3, 4]) +// [ -0, -1, -2, -3, -4 ] +``` + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-when) [`L.when((maybeValue, index) => testable) ~> traversal`](#L-when "L.when: ((Maybe a, Index) -> Boolean) -> PTraversal a a") v5.2.0 + +`L.when` allows one to selectively skip elements within a traversal. See also +[`L.unless`](#L-unless). + +For example: + +```js +L.modify([L.elems, L.when(x => x > 0)], R.negate, [0, -1, 2, -3, 4]) +// [ 0, -1, -2, -3, -4 ] +``` + +Note that `L.when(p)` is equivalent to [`L.choose((x, i) => p(x, i) ? +L.identity : L.zero)`](#L-choose). + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-zero) [`L.zero ~> traversal`](#L-zero "L.zero: PTraversal s a") v6.0.0 + +`L.zero` is a traversal of no elements and is the identity element of +[`L.choice`](#L-choice) and [`L.chain`](#L-chain). + +For example: + +```js +L.collect( + [L.elems, L.cond([R.is(Array), L.elems], [R.is(Object), 'x'], [L.zero])], + [1, {x: 2}, [3, 4]] +) +// [ 2, 3, 4 ] +``` + #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#folds-over-traversals) [Folds over traversals](#folds-over-traversals) ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-all) [`L.all((maybeValue, index) => testable, traversal, maybeData) ~> boolean`](#L-all "L.all: ((Maybe a, Index) -> Boolean) -> PTraversal s a -> Boolean") v9.6.0 @@ -2351,7 +2363,7 @@ L.all( ``` See also: [`L.any`](#L-any), [`L.none`](#L-none), and -[`L.selectAs`](#L-selectAs). +[`L.getAs`](#L-getAs). ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-and) [`L.and(traversal, maybeData) ~> boolean`](#L-and "L.and: PTraversal s Boolean -> Boolean") v9.6.0 @@ -2381,7 +2393,7 @@ L.any(x => x > 5, primitives, [[[1], 2], {y: 3}, [{l: 4, r: [5]}, {x: 6}]]) ``` See also: [`L.all`](#L-all), [`L.none`](#L-none), and -[`L.selectAs`](#L-selectAs). +[`L.getAs`](#L-getAs). ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-collect) [`L.collect(traversal, maybeData) ~> [...values]`](#L-collect "L.collect: PTraversal s a -> Maybe s -> [a]") v3.6.0 @@ -2588,6 +2600,58 @@ L.forEachWith(() => new Map(), (m, v, k) => m.set(k, v), L.values, {x: 2, y: 1}) Note that a new `Map` is returned each time the above expression is evaluated. +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-get) [`L.get(traversal, maybeData) ~> maybeValue`](#L-get "L.get: PTraversal s a -> Maybe s -> Maybe a") v9.8.0 + +`L.get` returns the element focused on by a [lens](#lenses) from a data +structure or goes lazily over the elements focused on by the given +[traversal](#traversals) and returns the first non-`undefined` element. See +also [`L.getLog`](#L-getLog). + +For example: + +```js +L.get('y', {x: 112, y: 101}) +// 101 +``` + +```js +L.get([L.elems, 'y'], [{x:1}, {y:2}, {z:3}]) +// 2 +``` + +Note that `L.get` is equivalent to [`L.getAs(x => x)`](#L-getAs). + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-getAs) [`L.getAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> maybeValue`](#L-getAs "L.getAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> Maybe b") v14.0.0 + +`L.getAs` goes lazily over the elements focused on by the given traversal, +applying the given function to each element, and returns the first +non-`undefined` value returned by the function. + +```js +L.getAs(x => x > 3 ? -x : undefined, L.elems, [3, 1, 4, 1, 5]) +// -4 +``` + +`L.getAs` operates lazily. The user specified function is only applied to +elements until the first non-`undefined` value is returned and after that +`L.getAs` returns without examining more elements. + +Note that `L.getAs` can be used to implement many other operations over +traversals such as finding an element matching a predicate and checking whether +all/any elements match a predicate. For example, here is how you could +implement a for all predicate over traversals: + +```js +const all = (p, t, s) => !L.getAs(x => p(x) ? undefined : true, t, s) +``` + +Now: + +```js +all(x => x < 9, primitives, [[[1], 2], {y: 3}, [{l: 4, r: [5]}, {x: 6}]]) +// true +``` + ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-isDefined) [`L.isDefined(traversal, maybeData) ~> boolean`](#L-isDefined "L.isDefined: PTraversal s a -> Maybe s -> Boolean") v11.8.0 `L.isDefined` determines whether or not the given traversal focuses on any @@ -2736,7 +2800,7 @@ L.none(x => x > 5, primitives, [[[1], 2], {y: 3}, [{l: 4, r: [5]}, {x: 6}]]) // false ``` -See also: [`L.all`](#L-all), [`L.any`](#L-any), and [`L.selectAs`](#L-selectAs). +See also: [`L.all`](#L-all), [`L.any`](#L-any), and [`L.getAs`](#L-getAs). ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-or) [`L.or(traversal, maybeData) ~> boolean`](#L-or "L.or: PTraversal s Boolean -> Boolean") v9.6.0 @@ -2781,7 +2845,10 @@ Note that unlike many other folds, `L.productAs` expects the function to only return numbers and `undefined` is not treated in a special way. If you need to skip elements, you can return the number `1`. -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-select) [`L.select(traversal, maybeData) ~> maybeValue`](#L-select "L.select: PTraversal s a -> Maybe s -> Maybe a") v9.8.0 +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-select) ~~[`L.select(traversal, maybeData) ~> maybeValue`](#L-select "L.select: PTraversal s a -> Maybe s -> Maybe a") v9.8.0~~ + +**WARNING: `L.select` has been obsoleted. Just use [`L.get`](#L-get). See +[CHANGELOG](./CHANGELOG.md#1400) for details.** `L.select` goes lazily over the elements focused on by the given traversal and returns the first non-`undefined` element. @@ -2793,7 +2860,10 @@ L.select([L.elems, 'y'], [{x:1}, {y:2}, {z:3}]) Note that `L.select` is equivalent to [`L.selectAs(x => x)`](#L-selectAs). -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-selectAs) [`L.selectAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> maybeValue`](#L-selectAs "L.selectAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> Maybe b") v9.8.0 +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-selectAs) ~~[`L.selectAs((maybeValue, index) => maybeValue, traversal, maybeData) ~> maybeValue`](#L-selectAs "L.selectAs: ((Maybe a, Index) -> Maybe b) -> PTraversal s a -> Maybe s -> Maybe b") v9.8.0~~ + +**WARNING: `L.selectAs` has been obsoleted. Just use [`L.getAs`](#L-getAs). +See [CHANGELOG](./CHANGELOG.md#1400) for details.** `L.selectAs` goes lazily over the elements focused on by the given traversal, applying the given function to each element, and returns the first @@ -2856,22 +2926,6 @@ elements, you can return the number `0`. Lenses always have a single focus which can be [viewed](#L-get) directly. Put in another way, a lens specifies a path to a single element in a data structure. -#### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#operations-on-lenses) [Operations on lenses](#operations-on-lenses) - -##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-get) [`L.get(lens, maybeData) ~> maybeValue`](#L-get "L.get: PLens s a -> Maybe s -> Maybe a") v2.2.0 - -`L.get` returns the element focused on by a [lens](#lenses) from a data -structure. - -For example: - -```js -L.get('y', {x: 112, y: 101}) -// 101 -``` - -Note that `L.get` does not work on [traversals](#traversals). - #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#creating-new-lenses) [Creating new lenses](#creating-new-lenses) ##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-lens) [`L.lens((maybeData, index) => maybeValue, (maybeValue, maybeData, index) => maybeData) ~> lens`](#L-lens "L.lens: ((Maybe s, Index) -> Maybe a) -> ((Maybe a, Maybe s, Index) -> Maybe s) -> PLens s a") v1.0.0 @@ -4515,9 +4569,9 @@ L.joinAs( ## [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#deepening-topics) [Deepening topics](#deepening-topics) -### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#understanding-filter-find-select-and-when) [Understanding `L.filter`, `L.find`, `L.select`, and `L.when`](#understanding-filter-find-select-and-when) +### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#understanding-filter-find-select-and-when) [Understanding `L.filter`, `L.find`, `L.get`, and `L.when`](#understanding-filter-find-get-and-when) -The [`L.filter`](#L-filter), [`L.find`](#L-find), [`L.select`](#L-select), and +The [`L.filter`](#L-filter), [`L.find`](#L-find), [`L.get`](#L-get), and [`L.when`](#L-when) serve related, but different, purposes and it is important to understand their differences in order to make best use of them. @@ -4527,24 +4581,23 @@ Here is a table of their call patterns and type signatures: | ------------------------------------------ | ---------------------------------------------------------- | `L.filter((value, index) => bool) ~> lens` | `L.filter: ((Maybe a, Index) -> Boolean) -> PLens [a] [a]` | `L.find((value, index) => bool) ~> lens` | `L.find: ((Maybe a, Index) -> Boolean) -> PLens [a] a` -| `L.select(traversal, data) ~> value` | `L.select: PTraversal s a -> Maybe s -> Maybe a` +| `L.get(traversal, data) ~> value` | `L.get: PTraversal s a -> Maybe s -> Maybe a` | `L.when((value, index) => bool) ~> optic` | `L.when: ((Maybe a, Index) -> Boolean) -> POptic a a` As can be read from above, both [`L.filter`](#L-filter) and [`L.find`](#L-find) -introduce lenses, [`L.select`](#L-select) eliminates a traversal, and +introduce lenses, [`L.get`](#L-get) eliminates a traversal, and [`L.when`](#L-when) introduces an optic, which will always be a traversal in this section. We can also read that [`L.filter`](#L-filter) and -[`L.find`](#L-find) operate on arrays, while [`L.select`](#L-select) and +[`L.find`](#L-find) operate on arrays, while [`L.get`](#L-get) and [`L.when`](#L-when) operate on arbitrary traversals. Yet another thing to make -note of is that both [`L.find`](#L-find) and [`L.select`](#L-select) are -many-to-one while both [`L.filter`](#L-filter) and [`L.when`](#L-when) retain -cardinality. +note of is that both [`L.find`](#L-find) and [`L.get`](#L-get) are many-to-one +while both [`L.filter`](#L-filter) and [`L.when`](#L-when) retain cardinality. The following equations relate the operations in the read direction: ```jsx L.get([L.filter(p), 0]) = L.get(L.find(p)) - L.select([L.elems, L.when(p)]) = L.get(L.find(p)) + L.get([L.elems, L.when(p)]) = L.get(L.find(p)) L.collect([L.elems, L.when(p)]) = L.get(L.filter(p)) ``` @@ -4555,7 +4608,7 @@ an array identified by a given predicate. Despite the name, [`L.find`](#L-find) is probably not what one should use to generally search for something in a data structure. -[`L.select`](#L-select) (and [`L.selectAs`](#L-selectAs)) can be used to search +[`L.get`](#L-get) (and [`L.getAs`](#L-getAs)) can be used to search for an element in a data structure following an arbitrary traversal. That traversal can, of course, also make use of [`L.when`](#L-when) to filter elements or to limit the traversal. @@ -4752,16 +4805,38 @@ collections and back). #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#use-of-undefined) [Use of `undefined`](#use-of-undefined) -`undefined` is a natural choice in JavaScript, especially when dealing with -JSON, to represent nothingness. Some libraries use `null`, but that is arguably -a poor choice, because `null` is a valid JSON value. Some libraries implement -special `Maybe` types, but the benefits do not seem worth the trouble. First of -all, `undefined` already exists in JavaScript and is not a valid JSON value. -Inventing a new value to represent nothingness doesn't seem to add much. OTOH, -wrapping values with `Just` objects introduces a significant performance -overhead due to extra allocations. Operations with optics do not otherwise -necessarily require large numbers of allocations and can be made highly -efficient. +`undefined` is arguably a natural choice in JavaScript to represent nothingness: + +* `undefined` is the result of an attempt to access non-existent properties of + objects. +* `undefined` is the result of functions that do not explicitly return another + value. +* `undefined` is not a valid JSON value and does not get mixed up with valid + JSON values. +* We can form a [monoid over JavaScript values by treating `undefined` as + zero](#L-Select). + +Some libraries use `null`, but that is arguably a poor choice, because `null` is +a valid JSON value, which means that when accessing JSON data a result of `null` +is ambiguous. + +One downside of using `undefined` is that it can sometimes be a valid value. +Fortunately this is fairly rarely the case so inventing a new value to represent +nothingness doesn't seem to add much. + +Some libraries implement special `Maybe` types, but the benefits do not seem +worth the trouble or the disadvantages in this context. The main disadvantage +is that wrapping values with `Just` objects introduces a significant performance +overhead due to extra allocations, because operations with optics do not +otherwise necessarily require large numbers of allocations and can be made +highly efficient. Also, a `Maybe` +[monad](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad) +is not necessary for optics. A +[monoid](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monoid) +is sufficient for optics based on +[applicatives](https://github.com/rpominov/static-land/blob/master/docs/spec.md#applicative), +because applicatives do not have a join operation and are not nested like +monads. Not having an explicit `Just` object means that dealing with values such as `Just Nothing` requires special consideration. @@ -5024,10 +5099,10 @@ pinch of salt and preferably try and measure your actual use cases! 200,548/s 19.27 O.Setter.set(o_x_y_z, 0, xyzn) 1,280,471/s 1.00 R.find(x => x > 3, xs100) - 1,066,129/s 1.20 L.selectAs(x => x > 3 ? x : undefined, L.elems, xs100) + 1,066,129/s 1.20 L.getAs(x => x > 3 ? x : undefined, L.elems, xs100) 2,529/s 506.25 O.Fold.findOf(O.Traversal.traversed, x => x > 3, xs100) - 9,325,674/s 1.00 L.selectAs(x => x < 3 ? x : undefined, L.elems, xs100) + 9,325,674/s 1.00 L.getAs(x => x < 3 ? x : undefined, L.elems, xs100) 4,411,876/s 2.11 R.find(x => x < 3, xs100) 2,473/s 3770.86 O.Fold.findOf(O.Traversal.traversed, x => x < 3, xs100) -- NO SHORTCUT EVALUATION @@ -5129,7 +5204,7 @@ pinch of salt and preferably try and measure your actual use cases! 49,965/s 1.00 L.modify(L.flatten, inc, xsss100) - 7,833,540/s 1.00 L.selectAs(x => x > 3 ? x : undefined, L.elems, pi) + 7,833,540/s 1.00 L.getAs(x => x > 3 ? x : undefined, L.elems, pi) 4,448,086/s 1.76 R.find(x => x > 3, pi) 32,770/s 239.05 O.Fold.findOf(O.Traversal.traversed, x => x > 3, pi) diff --git a/bench/bench.js b/bench/bench.js index 20fe5959..9cbee888 100644 --- a/bench/bench.js +++ b/bench/bench.js @@ -442,12 +442,12 @@ R.forEach( `U.set(['x', 'y', 'z'], 0, xyzn)` ], [ - `L.selectAs(x => x > 3 ? x : undefined, L.elems, xs100)`, + `L.getAs(x => x > 3 ? x : undefined, L.elems, xs100)`, `R.find(x => x > 3, xs100)`, `O.Fold.findOf(O.Traversal.traversed, x => x > 3, xs100)` ], [ - `L.selectAs(x => x < 3 ? x : undefined, L.elems, xs100)`, + `L.getAs(x => x < 3 ? x : undefined, L.elems, xs100)`, `R.find(x => x < 3, xs100)`, [ `O.Fold.findOf(O.Traversal.traversed, x => x < 3, xs100)`, @@ -577,7 +577,7 @@ R.forEach( `L.traverse(Ident, inc, L.leafs, xsss100)` ], [ - `L.selectAs(x => x > 3 ? x : undefined, L.elems, pi)`, + `L.getAs(x => x > 3 ? x : undefined, L.elems, pi)`, `R.find(x => x > 3, pi)`, `O.Fold.findOf(O.Traversal.traversed, x => x > 3, pi)` ], diff --git a/src/partial.lenses.js b/src/partial.lenses.js index 6bef162a..20fba0e3 100644 --- a/src/partial.lenses.js +++ b/src/partial.lenses.js @@ -50,6 +50,8 @@ const singletonPartial = x => (void 0 !== x ? [x] : x) const expect = (p, f) => copyName(x => (p(x) ? f(x) : void 0), f) +const freezeInDev = process.env.NODE_ENV === 'production' ? id : I.freeze + function deepFreeze(x) { if (I.isArray(x)) { x.forEach(deepFreeze) @@ -156,15 +158,26 @@ function selectInArrayLike(xi2v, xs) { // -const Select = { - map: I.sndU, - of: () => {}, - ap: (l, r) => (void 0 !== l ? l : r) +function Applicative(map, of, ap) { + if (!this) return freezeInDev(new Applicative(map, of, ap)) + this.map = map + this.of = of + this.ap = ap } -const ConcatOf = (ap, empty) => ({map: I.sndU, ap, of: I.always(empty)}) +const Monad = I.inherit(function Monad(map, of, ap, chain) { + if (!this) return freezeInDev(new Monad(map, of, ap, chain)) + Applicative.call(this, map, of, ap) + this.chain = chain +}, Applicative) + +// + +const ConstantWith = (ap, empty) => Applicative(I.sndU, I.always(empty), ap) + +const ConstantOf = ({concat, empty}) => ConstantWith(concat, empty()) -const Sum = ConcatOf(I.addU, 0) +const Sum = ConstantWith(I.addU, 0) const mumBy = ord => I.curry(function mumBy(xi2y, t, s) { @@ -264,15 +277,6 @@ const reqMaybeArray = msg => zs => { // -const reqApplicative = (name, arg) => C => { - if (!C.of) - errorGiven( - `\`${name}${arg ? `(${arg})` : ''}\` requires an applicative`, - C, - 'Note that you cannot `get` a traversal. Perhaps you wanted to `collect` it?' - ) -} - const reqMonad = name => C => { if (!C.chain) errorGiven( @@ -325,10 +329,18 @@ function traversePartialIndex(A, xi2yA, xs, skip) { // -const ConstantLog = { - map: (f, {m, p, c}) => ({m: `%O <= ${m}`, p: [f(p[0]), p], c}) -} -const getLogFn = x => ({m: '%O', p: [x, consExcept], c: x}) +const SelectLog = Applicative( + (f, {p, x, c}) => { + x = f(x) + if (!I.isFunction(x)) p = [x, p] + return {p, x, c} + }, + x => ({p: [], x, c: undefined}), + (l, r) => { + const v = undefined !== l.c ? l : r + return {p: v.p, x: l.x(r.x), c: v.c} + } +) // @@ -428,40 +440,17 @@ const modifyU = (process.env.NODE_ENV === 'production' const modifyAsyncU = (o, f, s) => returnAsync(toFunction(o)(s, void 0, IdentityAsync, f)) -function makeIx(i) { - const ix = (s, j) => ((ix.v = j), s) - ix.v = i - return ix -} - -function getNestedU(l, s, j, ix) { - for (let n = l.length, o; j < n; ++j) - switch (typeof (o = l[j])) { - case 'string': - s = getProp((ix.v = o), s) - break - case 'number': - s = getIndex((ix.v = o), s) - break - case 'object': - s = getNestedU(o, s, 0, ix) - break - default: - s = o(s, ix.v, Constant, ix) - } - return s -} - -const getU = (process.env.NODE_ENV === 'production' +const getAsU = (process.env.NODE_ENV === 'production' ? id - : C.par(0, C.ef(reqOptic)))((l, s) => { + : C.par(1, C.ef(reqOptic)))(function getAs(xi2y, l, s) { switch (typeof l) { case 'string': - return getProp(l, s) + return xi2y(getProp(l, s), l) case 'number': - return getIndex(l, s) - case 'object': - for (let i = 0, n = l.length, o; i < n; ++i) + return xi2y(getIndex(l, s), l) + case 'object': { + const n = l.length + for (let i = 0, o; i < n; ++i) switch (typeof (o = l[i])) { case 'string': s = getProp(o, s) @@ -470,11 +459,14 @@ const getU = (process.env.NODE_ENV === 'production' s = getIndex(o, s) break default: - return getNestedU(l, s, i, makeIx(l[i - 1])) + return composed(i, l)(s, l[i - 1], Select, xi2y) } - return s + return xi2y(s, l[n - 1]) + } default: - return l(s, void 0, Constant, id) + return xi2y !== id && l.length !== 4 + ? xi2y(l(s, void 0), void 0) + : l(s, void 0, Select, xi2y) } }) @@ -528,7 +520,7 @@ const getPick = (process.env.NODE_ENV === 'production' ? id : C.res(I.freeze))( let r for (const k in template) { const t = template[k] - const v = I.isObject(t) ? getPick(t, x) : getU(t, x) + const v = I.isObject(t) ? getPick(t, x) : getAsU(id, t, x) if (void 0 !== v) { if (!r) r = {} r[k] = v @@ -858,10 +850,7 @@ const orElseU = function orElse(back, prim) { } } -function zeroOp(y, i, C, xi2yC, x) { - const of = C.of - return of ? of(y) : C.map(I.always(y), xi2yC(x, i)) -} +const zero = (x, _i, C, _xi2yC) => C.of(x) // @@ -883,9 +872,6 @@ const pickInAux = (t, k) => [k, pickIn(t)] // -const condOfDefault = I.always(zeroOp) -const condOfCase = (p, o, r) => (y, j) => (p(y, j) ? o : r(y, j)) - // Auxiliary export const seemsArrayLike = x => @@ -894,30 +880,17 @@ export const seemsArrayLike = x => // Internals -export const Identity = (process.env.NODE_ENV === 'production' ? id : I.freeze)( - { - map: I.applyU, - of: id, - ap: I.applyU, - chain: I.applyU - } -) +export const Identity = Monad(I.applyU, id, I.applyU, I.applyU) -export const IdentityAsync = (process.env.NODE_ENV === 'production' - ? id - : I.freeze)({ - map: chainAsync, - ap: (xyP, xP) => chainAsync(xP => chainAsync(xyP => xyP(xP), xyP), xP), - of: id, - chain: chainAsync -}) - -export const Constant = (process.env.NODE_ENV === 'production' ? id : I.freeze)( - { - map: I.sndU - } +export const IdentityAsync = Monad( + chainAsync, + id, + (xyP, xP) => chainAsync(xP => chainAsync(xyP => xyP(xP), xyP), xP), + chainAsync ) +export const Select = ConstantWith((l, r) => (void 0 !== l ? l : r)) + export const toFunction = (process.env.NODE_ENV === 'production' ? id : C.par(0, C.ef(reqOptic)))(function toFunction(o) { @@ -1017,17 +990,39 @@ export const condOf = (process.env.NODE_ENV === 'production' return fn(of, ...cs) })(function condOf(of) { of = toFunction(of) - let op = condOfDefault - let n = arguments.length - while (--n) { - const c = arguments[n] - op = - c.length === 1 - ? I.always(toFunction(c[0])) - : condOfCase(c[0], toFunction(c[1]), op) + + let n = arguments.length - 1 + if (!n) return zero + + let def = arguments[n] + if (def.length === 1) { + --n + def = toFunction(def[0]) + } else { + def = zero } - return function condOf(x, i, C, xi2yC) { - return of(x, i, Constant, op)(x, i, C, xi2yC) + + const ps = Array(n) + const os = Array(n + 1) + for (let i = 0; i < n; ++i) { + const c = arguments[i + 1] + ps[i] = c[0] + os[i] = toFunction(c[1]) + } + os[n] = def + + return function condOf(x, i, F, xi2yF) { + let min = n + of(x, i, Select, (y, j) => { + for (let i = 0; i < min; ++i) { + if (ps[i](y, j)) { + min = i + if (i === 0) return 0 + else break + } + } + }) + return os[min](x, i, F, xi2yF) } }) @@ -1035,23 +1030,6 @@ export const ifElse = I.curry(function ifElse(c, t, e) { return eitherU(toFunction(t), toFunction(e))(c) }) -export const iftes = (process.env.NODE_ENV === 'production' - ? id - : fn => - function iftes(_c, _t) { - warn( - iftes, - '`iftes` has been obsoleted. Use `ifElse` or `cond` instead. See CHANGELOG for details.' - ) - return fn.apply(null, arguments) - })(function iftes(_c, _t) { - let n = arguments.length - let r = n & 1 ? toFunction(arguments[--n]) : zero - while (0 <= (n -= 2)) - r = eitherU(toFunction(arguments[n + 1]), r)(arguments[n]) - return r -}) - export const orElse = I.curry(orElseU) // Querying @@ -1062,13 +1040,13 @@ export const chain = I.curry(function chain(xi2yO, xO) { export const choice = (...os) => os.reduceRight(orElseU, zero) -export const unless = eitherU(zeroOp, identity) +export const unless = eitherU(zero, identity) -export const when = eitherU(identity, zeroOp) +export const when = eitherU(identity, zero) export const optional = when(I.isDefined) -export const zero = (x, i, C, xi2yC) => zeroOp(x, i, C, xi2yC) +export {zero} // Indices @@ -1099,11 +1077,13 @@ export const skipIx = setName(tieIx(I.sndU), 'skipIx') // Debugging -export const getLog = I.curry(function getLog(l, s) { - const {m, p, c} = traverseU(ConstantLog, getLogFn, l, s) - console.log.apply(console, pushTo(p, [m])) +export function getLog(l, s) { + let {p, c} = traverseU(SelectLog, x => ({p: [x, consExcept], x, c: x}), l, s) + p = pushTo(p, ['%O']) + for (let i = 2; i < p.length; ++i) p[0] += ' <= %O' + console.log.apply(console, p) return c -}) +} export function log() { const show = I.curry(function log(dir, x) { @@ -1148,13 +1128,13 @@ export const seq = (process.env.NODE_ENV === 'production' export const assignOp = x => [propsOf(x), setOp(x)] export const modifyOp = xi2y => - function modifyOp(x, i, C, xi2yC) { - return zeroOp((x = xi2y(x, i)), i, C, xi2yC, x) + function modifyOp(x, i, C, _xi2yC) { + return C.of(xi2y(x, i)) } export const setOp = y => - function setOp(_x, i, C, xi2yC) { - return zeroOp(y, i, C, xi2yC, y) + function setOp(_x, _i, C, _xi2yC) { + return C.of(y) } export const removeOp = setOp() @@ -1183,11 +1163,9 @@ export function branches() { // Traversals and combinators -export const elems = (process.env.NODE_ENV === 'production' - ? id - : C.par(2, C.ef(reqApplicative('elems'))))(function elems(xs, i, A, xi2yA) { +export function elems(xs, i, A, xi2yA) { return seemsArrayLike(xs) ? elemsI(xs, i, A, xi2yA) : A.of(xs) -}) +} export const elemsTotal = (xs, i, A, xi2yA) => seemsArrayLike(xs) @@ -1202,12 +1180,7 @@ export const entries = setName(toFunction([keyed, elems]), 'entries') export const keys = setName(toFunction([keyed, elems, 0]), 'keys') -export const matches = (process.env.NODE_ENV === 'production' - ? id - : C.dep( - re => - re.global ? C.res(C.par(2, C.ef(reqApplicative('matches', re)))) : id - ))(function matches(re) { +export function matches(re) { return function matches(x, _i, C, xi2yC) { if (I.isString(x)) { const {map} = C @@ -1230,43 +1203,25 @@ export const matches = (process.env.NODE_ENV === 'production' ) } } - return zeroOp(x, void 0, C, xi2yC) + return C.of(x) } -}) +} -export const values = (process.env.NODE_ENV === 'production' - ? id - : C.par(2, C.ef(reqApplicative('values'))))( - setName(branchOr1Level(identity, I.protoless0), 'values') -) +export const values = setName(branchOr1Level(identity, I.protoless0), 'values') -export const children = (process.env.NODE_ENV === 'production' - ? id - : C.par(2, C.ef(reqApplicative('children'))))(function children( - x, - i, - C, - xi2yC -) { +export function children(x, i, C, xi2yC) { return I.isArray(x) ? elemsI(x, i, C, xi2yC) : I.isObject(x) ? values(x, i, C, xi2yC) : C.of(x) -}) +} -export const flatten = (process.env.NODE_ENV === 'production' - ? id - : C.par(2, C.ef(reqApplicative('flatten'))))(function flatten( - x, - i, - C, - xi2yC -) { +export function flatten(x, i, C, xi2yC) { const rec = (x, i) => I.isArray(x) ? elemsI(x, i, C, rec) : void 0 !== x ? xi2yC(x, i) : C.of(x) return rec(x, i) -}) +} export function query() { const r = [] @@ -1290,8 +1245,7 @@ export const leafs = satisfying( // Folds over traversals export const all = I.curry(function all(xi2b, t, s) { - return !traverseU( - Select, + return !getAsU( (x, i) => { if (!xi2b(x, i)) return true }, @@ -1303,8 +1257,7 @@ export const all = I.curry(function all(xi2b, t, s) { export const and = all(id) export const any = I.curry(function any(xi2b, t, s) { - return !!traverseU( - Select, + return !!getAsU( (x, i) => { if (xi2b(x, i)) return true }, @@ -1331,9 +1284,7 @@ export const collectAs = (process.env.NODE_ENV === 'production' export const collect = collectAs(id) -export const concatAs = mkTraverse(id, function concatAs(m) { - return ConcatOf(m.concat, m.empty()) -}) +export const concatAs = mkTraverse(id, ConstantOf) export const concat = concatAs(id) @@ -1412,12 +1363,18 @@ export const forEachWith = I.curry(function forEachWith(newC, ef, t, s) { return c }) +export function get(l, s) { + return 1 < arguments.length ? getAsU(id, l, s) : s => getAsU(id, l, s) +} + +export const getAs = I.curry(getAsU) + export const isDefined = I.curry(function isDefined(t, s) { - return void 0 !== traverseU(Select, id, t, s) + return void 0 !== getAsU(id, t, s) }) export const isEmpty = I.curry(function isEmpty(t, s) { - return !traverseU(Select, toTrue, t, s) + return !getAsU(toTrue, t, s) }) export const joinAs = mkTraverse( @@ -1428,7 +1385,7 @@ export const joinAs = mkTraverse( 0, C.ef(reqString('`join` and `joinAs` expect a string delimiter')) ))(function joinAs(d) { - return ConcatOf( + return ConstantWith( (x, y) => (void 0 !== x ? (void 0 !== y ? x + d + y : x) : y) ) }) @@ -1465,8 +1422,7 @@ export const minimumBy = mumBy(I.ltU) export const minimum = minimumBy(id) export const none = I.curry(function none(xi2b, t, s) { - return !traverseU( - Select, + return !getAsU( (x, i) => { if (xi2b(x, i)) return true }, @@ -1477,24 +1433,36 @@ export const none = I.curry(function none(xi2b, t, s) { export const or = any(id) -export const productAs = traverse(ConcatOf(I.multiplyU, 1)) +export const productAs = traverse(ConstantWith(I.multiplyU, 1)) export const product = productAs(unto(1)) -export const selectAs = traverse(Select) +export const select = + process.env.NODE_ENV === 'production' + ? get + : I.curry(function select(l, s) { + warn( + select, + '`select` has been obsoleted. Just use `get`. See CHANGELOG for details.' + ) + return get(l, s) + }) -export const select = selectAs(id) +export const selectAs = + process.env.NODE_ENV === 'production' + ? getAs + : I.curry(function selectAs(f, l, s) { + warn( + selectAs, + '`selectAs` has been obsoleted. Just use `getAs`. See CHANGELOG for details.' + ) + return getAs(f, l, s) + }) export const sumAs = traverse(Sum) export const sum = sumAs(unto0) -// Operations on lenses - -export function get(l, s) { - return 1 < arguments.length ? getU(l, s) : s => getU(l, s) -} - // Creating new lenses export const lens = I.curry(lensU) @@ -1736,7 +1704,7 @@ export const array = elem => { } export const inverse = iso => (x, i, F, xi2yF) => - F.map(x => getU(iso, x), xi2yF(setU(iso, x, void 0), i)) + F.map(x => getAsU(id, iso, x), xi2yF(setU(iso, x, void 0), i)) // Basic isomorphisms @@ -1907,7 +1875,7 @@ export const subtract = c => numberIsoU(I.add(-c), I.add(c)) // Interop export const pointer = s => { - if (s[0] === '#') s = getU(uriComponent, s) + if (s[0] === '#') s = getAsU(id, uriComponent, s) const ts = s.split('/') const n = ts.length for (let i = 1; i < n; ++i) { diff --git a/test/tests.js b/test/tests.js index d232e716..3594dcd5 100644 --- a/test/tests.js +++ b/test/tests.js @@ -194,6 +194,10 @@ describe('L.log', () => { describe('L.getLog', () => { testEq(() => L.getLog(['x', 0, 'y'], {x: [{y: 101}]}), 101) + testEq( + () => L.getLog(['data', L.elems, 'y'], {data: [{x: 1}, {y: 2}, {y: 3}]}), + 2 + ) }) describe('L.compose', () => { @@ -219,9 +223,9 @@ describe('L.identity', () => { describe('arities', () => { const arities = { - Constant: undefined, Identity: undefined, IdentityAsync: undefined, + Select: undefined, add: 1, all: 3, and: 2, @@ -271,12 +275,12 @@ describe('arities', () => { forEach: 3, forEachWith: 4, get: 2, + getAs: 3, getInverse: 2, getLog: 2, getter: 1, identity: 4, ifElse: 3, - iftes: 2, index: 1, indexed: 4, inverse: 1, @@ -570,7 +574,7 @@ describe('L.setter', () => { describe('L.zero', () => { testEq(() => L.get(L.zero, 'anything'), undefined) - testEq(() => L.get([L.zero, L.valueOr('whatever')], 'anything'), 'whatever') + testEq(() => L.get([L.zero, L.valueOr('whatever')], 'anything'), undefined) testEq(() => L.set(L.zero, 'anything', 'original'), 'original') testEq(() => L.collect([L.elems, L.zero], [1, 3]), []) testEq(() => L.remove([L.elems, L.zero], [1, 2]), [1, 2]) @@ -908,7 +912,7 @@ describe('L.optional', () => { describe('L.when', () => { testEq(() => L.get(L.when(x => x > 2), 1), undefined) - testEq(() => L.get([L.when(x => x > 2), I.always(2)], 1), 2) + testEq(() => L.get([L.when(x => x > 2), I.always(2)], 1), undefined) testEq(() => L.get(L.when(x => x > 2), 3), 3) testEq(() => L.collect([L.elems, L.when(x => x > 2)], [1, 3, 2, 4]), [3, 4]) testEq( @@ -919,7 +923,7 @@ describe('L.when', () => { describe('L.unless', () => { testEq(() => L.get(L.unless(x => x <= 2), 1), undefined) - testEq(() => L.get([L.unless(x => x <= 2), I.always(2)], 1), 2) + testEq(() => L.get([L.unless(x => x <= 2), I.always(2)], 1), undefined) testEq(() => L.get(L.unless(x => x <= 2), 3), 3) testEq(() => L.collect([L.elems, L.unless(x => x <= 2)], [1, 3, 2, 4]), [ 3, @@ -1408,12 +1412,13 @@ describe('L.seq', () => { }) describe('lazy folds', () => { - testEq(() => L.select(flatten, [[[[[[[[[[101]]]]]]]]]]), 101) - testEq(() => L.select(L.elems, []), undefined) - testEq(() => L.select(L.values, {}), undefined) + testEq(() => L.get([L.elems, 'y'], [{x: 1}, {y: 2}, {z: 3}]), 2) + testEq(() => L.get(flatten, [[[[[[[[[[101]]]]]]]]]]), 101) + testEq(() => L.get(L.elems, []), undefined) + testEq(() => L.get(L.values, {}), undefined) testEq( () => - L.selectAs((x, i) => (x > 3 ? [x + 2, i] : undefined), L.elems, [ + L.getAs((x, i) => (x > 3 ? [x + 2, i] : undefined), L.elems, [ 3, 1, 4, @@ -1424,7 +1429,7 @@ describe('lazy folds', () => { ) testEq( () => - L.selectAs((x, i) => (x > 3 ? [x + 2, i] : undefined), L.values, { + L.getAs((x, i) => (x > 3 ? [x + 2, i] : undefined), L.values, { a: 3, b: 1, c: 4, @@ -1433,10 +1438,10 @@ describe('lazy folds', () => { }), [6, 'c'] ) - testEq(() => L.selectAs(_ => {}, L.values, {x: 1}), undefined) + testEq(() => L.getAs(_ => {}, L.values, {x: 1}), undefined) testEq( () => - L.selectAs(x => (x < 9 ? undefined : [x]), flatten, [ + L.getAs(x => (x < 9 ? undefined : [x]), flatten, [ [[1], 2], {y: 3}, [{l: 41, r: [5]}, {x: 6}] @@ -1446,7 +1451,7 @@ describe('lazy folds', () => { testEq( () => { let n = 0 - const v = X.selectAs( + const v = X.getAs( x => { n += 1 return x === 42 ? x : undefined @@ -1461,7 +1466,7 @@ describe('lazy folds', () => { testEq( () => { let n = 0 - const v = X.selectAs( + const v = X.getAs( x => { n += 1 return x === 42 ? x : undefined @@ -1476,7 +1481,7 @@ describe('lazy folds', () => { testEq( () => { let n = 0 - const v = X.selectAs( + const v = X.getAs( x => { n += 1 return x === 42 ? x : undefined @@ -1491,7 +1496,7 @@ describe('lazy folds', () => { testEq( () => { let n = 0 - const v = X.selectAs( + const v = X.getAs( x => { n += 1 return x === 'ba' ? x : undefined @@ -1630,7 +1635,7 @@ describe('transforming', () => { L.transform([L.elems, L.when(x => x > 3), L.removeOp], [3, 1, 4, 1, 5]), [3, 1, 1] ) - testEq(() => L.get(L.setOp(42), 101), 42) + testEq(() => L.get(L.setOp(42), 101), undefined) testEq(() => L.set(L.setOp(42), 96, 101), 42) }) @@ -1706,38 +1711,47 @@ describe('L.condOf', () => { [42, 101] ) testEq(() => L.get(L.condOf([]), 'anything'), undefined) -}) - -describe('L.ifElse', () => { - testEq(() => L.set(L.ifElse(R.not, L.setOp(1), L.zero), 3, 2), 2) - testEq(() => L.set(L.ifElse(R.not, L.setOp(1), L.zero), 3, 0), 1) - testEq(() => L.transform(L.ifElse(R.not, L.setOp(1), L.setOp(0)), null), 1) - testEq(() => L.transform(L.ifElse(R.not, L.setOp(1), L.setOp(0)), 2), 0) -}) - -describe('L.iftes', () => { - testEq(() => L.set(L.iftes(R.not, L.setOp(1)), 3, 2), 2) - testEq(() => L.set(L.iftes(R.not, L.setOp(1)), 3, 0), 1) - testEq(() => L.transform(L.iftes(R.not, L.setOp(1), L.setOp(0)), null), 1) - testEq(() => L.transform(L.iftes(R.not, L.setOp(1), L.setOp(0)), 2), 0) testEq( () => - L.transform( - L.iftes(R.equals(1), L.setOp(-1), R.equals(2), L.setOp(1), L.setOp(2)), - -1 + L.get( + L.condOf( + ['c', L.elems], + [R.equals(1), ['d', 0]], + [R.equals(2), ['d', 1]], + [R.equals(3), ['d', 2]] + ), + { + c: [3, 1, 2], + d: ['a', 'b', 'c'] + } ), - 2 + 'a' ) testEq( () => - L.transform( - L.iftes(R.equals(1), L.setOp(-1), R.equals(2), L.setOp(1), L.setOp(2)), - 2 + L.get( + L.condOf( + ['c', L.elems], + [R.equals(1), ['d', 0]], + [R.equals(2), ['d', 1]], + [R.equals(3), ['d', 2]] + ), + { + c: [3, -1, 2], + d: ['a', 'b', 'c'] + } ), - 1 + 'b' ) }) +describe('L.ifElse', () => { + testEq(() => L.set(L.ifElse(R.not, L.setOp(1), L.zero), 3, 2), 2) + testEq(() => L.set(L.ifElse(R.not, L.setOp(1), L.zero), 3, 0), 1) + testEq(() => L.transform(L.ifElse(R.not, L.setOp(1), L.setOp(0)), null), 1) + testEq(() => L.transform(L.ifElse(R.not, L.setOp(1), L.setOp(0)), 2), 0) +}) + describe('L.singleton', () => { testEq(() => L.get(L.singleton, ['too', 'long']), undefined) testEq(() => L.get(L.singleton, 'too-long'), undefined) @@ -2141,11 +2155,11 @@ describe('ix', () => { } ) - testEq( - () => L.selectAs((v, i) => [v, i], ['foo', L.setIx('bar')], {foo: 101}), - [101, 'bar'] - ) - testEq(() => L.selectAs(x => x + 1, x => x * 2, 3), 7) + testEq(() => L.getAs((v, i) => [v, i], ['foo', L.setIx('bar')], {foo: 101}), [ + 101, + 'bar' + ]) + testEq(() => L.getAs(x => x + 1, x => x * 2, 3), 7) }) describe('async', () => { @@ -2199,11 +2213,6 @@ if (process.env.NODE_ENV !== 'production') { testThrows(() => X.prop(x => x)) testThrows(() => X.prop()) - testThrows(() => L.get(L.elems, [])) - testThrows(() => L.get(L.values, {})) - testThrows(() => L.get(L.branch({a: []}), {})) - testThrows(() => L.get(L.matches(/a/g), 'foo')) - testThrows(() => L.set(L.props('length'), 'lol', undefined)) testThrows(() => L.set(L.slice(undefined, undefined), 11, [])) testThrows(() => L.pick(new XYZ(1, 2, 3))) @@ -2259,3 +2268,11 @@ describe('cloning avoidance', () => { z: NaN }) }) + +describe('obsoleted', () => { + testEq(() => L.select([L.elems, 'x'], [{}, {x: 101}]), 101) + testEq( + () => L.selectAs(x => x + 1, [L.elems, 'x', L.optional], [{}, {x: 100}]), + 101 + ) +}) diff --git a/test/types.js b/test/types.js index 23b33f2b..33575299 100644 --- a/test/types.js +++ b/test/types.js @@ -66,12 +66,12 @@ const template = c => T.lazy(rec => T.props(T.or(c, rec))) // Internals -export const Constant = T_functor - export const Identity = T_monad export const IdentityAsync = T_monad +export const Select = T_applicative + export const toFunction = T.fn( [T_optic], T_opticFnOf(T.or(T_monad, T_applicative, T_functor)) @@ -119,7 +119,6 @@ export const ifElse = T.fn( [T.fn([T_maybeDataO, T_index], T.any), T_optic, T_optic], T_optic ) -export const iftes = T.fnVarN(2, T.any, T_optic) export const orElse = T.fn([T_optic, T_optic], T_optic) // Querying @@ -141,7 +140,7 @@ export const tieIx = T.fn([T.fn([T_index, T_index], T_index), T_optic], T_optic) // Debugging -export const getLog = T.fn([T_lens, T_maybeDataI], T_maybeDataO) +export const getLog = T.fn([T_traversal, T_maybeDataI], T_maybeDataO) export const log = T.fnVarN(0, T.string, T_optic) // Operations on transforms @@ -246,6 +245,13 @@ export const forEachWith = T.fn( T.any ) +export const get = T.fn([T_traversal, T_maybeDataI], T_maybeDataO) + +export const getAs = T.fn( + [T.fn([T_maybeDataO, T_index], T.any), T_traversal, T_maybeDataI], + T.any +) + export const isDefined = T.fn([T_traversal, T_maybeDataI], T.boolean) export const isEmpty = T.fn([T_traversal, T_maybeDataI], T.boolean) @@ -299,10 +305,6 @@ export const selectAs = T.fn( T.any ) -// Operations on lenses - -export const get = T.fn([T_lens, T_maybeDataI], T_maybeDataO) - // Creating new lenses export const lens = T.fn(