From 97df2fc6cee69320cd3d4094ef300dabac5506a8 Mon Sep 17 00:00:00 2001 From: Vesa Karvonen Date: Fri, 2 Feb 2018 13:43:01 +0200 Subject: [PATCH] Added `retry` --- README.md | 73 +++++++++++++++++++++++++++++++++---------- bench/bench.js | 9 ++++++ src/partial.lenses.js | 15 +++++++++ test/tests.js | 11 +++++++ test/types.js | 7 +++++ 5 files changed, 98 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 397eb784..3f05d26c 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html) * [`L.transform(optic, maybeData) ~> maybeData`](#L-transform "L.transform: POptic s a -> Maybe s -> Maybe s") v11.7.0 * [Sequencing](#sequencing) * [`L.seq(...transforms) ~> transform`](#L-seq "L.seq: (...PTransform s a) -> PTransform s a") v9.4.0 + * [Backtracking](#backtracking) + * [`L.retry((maybeValue, index) => transform, transform) ~> transform`](#L-retry "L.retry: ((Maybe t, Index) -> PTransform s u) -> PTransform s t -> PTransform s u") v13.3.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 @@ -856,15 +858,16 @@ Now, optics are composable in several ways and in each of those ways there is an operation to perform the composition and laws on how such composed optics behave. Here is a table of the means of composition supported by this library: -| | Operation(s) | Semantics -| ------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- -| [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) -| 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) +| | Operation(s) | Semantics +| ----------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- +| [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) +| 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) +| [Backtracking](#backtracking) | [`L.retry((maybeValue, index) => transform, transform)`](#L-retry) | Monad over [transforms](#transforms) The above table and, in particular, the semantics column is by no means complete. In particular, the documentation of this library does not generally @@ -1381,18 +1384,23 @@ empty or read-only [zero](#L-zero). `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 +combinator for querying with optics. `L.chain(y2yzO, xyO)` is equivalent to ```jsx -L.compose(optic, L.choose((maybeValue, index) => - maybeValue === undefined +L.compose(xyO, L.choose((yMaybe, index) => + yMaybe === undefined ? L.zero - : toOptic(maybeValue, index))) + : y2yzO(yMaybe, 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. +optics as providing a +[Monad](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad) +and +[Alternative](https://github.com/rpominov/static-land/blob/master/docs/spec.md#alternative) +aka +[MonadPlus](https://en.wikibooks.org/wiki/Haskell/Alternative_and_MonadPlus#MonadPlus). ##### [≡](#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 @@ -1645,11 +1653,42 @@ combined together as a [`Monad`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad) ```jsx -chain(x2t, t) = L.seq(t, L.choose(x2t)) - of(x) = L.setOp(x) +chain(y2yzt, xyT) = L.seq(xyT, L.choose(y2yzT)) + of(x) = L.setOp(x) +``` + +which is not the same as the [querying monad](#L-chain) or the [backtracking +monad](#L-retry). + +#### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#backtracking) Backtracking + +The [`L.retry`](#L-retry) combinator allows one to build +[transforms](#transforms) that modify their focus in multiple different ways and +choose one of the results. + +##### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#L-retry) [`L.retry((maybeValue, index) => transform, transform) ~> transform`](#L-retry "L.retry: ((Maybe t, Index) -> PTransform s u) -> PTransform s t -> PTransform s u") v13.3.0 + +`L.retry` creates a transform that first modifies the data in the focus with +given transform and then modifies the same data in the focus with the transform +returned by the given function. + +Note that `L.retry` with [`L.seqOp`](#L-setOp) forms another +[`Monad`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad): + +```jsx +chain(y2xyT, xyT) = L.retry(x2xyT, xyT) + of(x) = L.setOp(x) ``` -which is not the same as the [querying monad](#L-chain). +which is not the same as the [querying monad](#L-chain) or the [sequencing +monad](#L-seq). + +Note that `L.retry(t2suT, stT)` can be implemented using [`L.choose`](#L-choose) +and [`L.seq`](#L-seq): + +```jsx +L.choose((s, i) => L.seq(stT, L.choose(t => L.seq(L.setOp(s), t2suT(t, i))))) +``` #### [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/index.html#transforming) Transforming diff --git a/bench/bench.js b/bench/bench.js index 29f28950..4fefc8bb 100644 --- a/bench/bench.js +++ b/bench/bench.js @@ -544,6 +544,15 @@ R.forEach( L.seq(L.modifyOp(dec), L.choose(n => n === 0 ? L.identity : rec))), 100)` + ], + [ + `L.transform(I.seq(L.identity, + L.retry(L.setOp), + L.retry(L.setOp), + L.retry(L.setOp), + L.retry(L.setOp), + L.retry(L.setOp)), + 101)` ] ] ) diff --git a/src/partial.lenses.js b/src/partial.lenses.js index 86a29d6a..b26f3c04 100644 --- a/src/partial.lenses.js +++ b/src/partial.lenses.js @@ -894,6 +894,21 @@ export const seq = (process.env.NODE_ENV === 'production' return r }) +// Backtracking + +export const retry = (process.env.NODE_ENV === 'production' + ? I.curry + : C.res(C.par(2, C.ef(reqMonad('retry')))))( + (yi2xzT, xyT) => ( + (xyT = toFunction(xyT)), + (x, i, M, xi2yM) => + M.chain( + y => toFunction(yi2xzT(y, i))(x, i, M, xi2yM), + xyT(x, i, M, xi2yM) + ) + ) +) + // Creating new traversals export const branchOr = (process.env.NODE_ENV === 'production' diff --git a/test/tests.js b/test/tests.js index b1de0545..beb3a369 100644 --- a/test/tests.js +++ b/test/tests.js @@ -284,6 +284,7 @@ describe('arities', () => { replace: 2, required: 1, reread: 1, + retry: 2, reverse: 4, rewrite: 1, seemsArrayLike: 1, @@ -1211,6 +1212,16 @@ describe('L.seq', () => { testEq(`collectM(L.seq(1, 0, 2), ["b", "a", "c"])`, ['a', 'b', 'c']) }) +describe('L.retry', () => { + testEq( + `L.transform([L.values, + L.retry((x, i) => L.modifyOp((y, i) => [{x, i}, {y, i}]), + L.modifyOp((x, i) => ({x, i})))], + {x: 42})`, + {x: [{x: {x: 42, i: 'x'}, i: 'x'}, {y: 42, i: 'x'}]} + ) +}) + describe('lazy folds', () => { testEq(`L.select(flatten, [[[[[[[[[[101]]]]]]]]]])`, 101) testEq(`L.select(L.elems, [])`, undefined) diff --git a/test/types.js b/test/types.js index 18328f57..17cfdf2b 100644 --- a/test/types.js +++ b/test/types.js @@ -137,6 +137,13 @@ export const log = T.fnVarN(0, T.string, T_optic) export const seq = T.fnVarN(0, T_optic, T_transform) +// Backtracking + +export const retry = T.fn( + [T.fn([T_maybeDataO, T_index], T_transform), T_transform], + T_transform +) + // Creating new traversals export const branchOr = T.fn([T_optic, template(T_traversal)], T_traversal)