Skip to content

Commit

Permalink
Added attrsOr, attrs, and object
Browse files Browse the repository at this point in the history
  • Loading branch information
polytypic committed Aug 14, 2018
1 parent ac723a1 commit 95b00bf
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 39 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Partial Lenses Changelog

## 14.x.y

Obsoleted `L.pickIn`, `L.props`, and `L.propsOf`. They are based on `L.pick`,
which uses lenses and requires super linear time when written through. The new
`L.attrs`, `L.attrsNamed`, and `L.attrsOf` work in linear time and differ from
them in the handling of empty results and removal, because they are designed to
work symmetrically as isomorphisms, but in most cases you should be able to just
replace uses of `L.pickIn` with `L.attrs`, `L.props` with `L.attrsNamed`, and
`L.propsOf` with `L.attrsOf`:

```diff
-L.pickIn(template)
+L.attrs(template)
```

```diff
-L.props(...propNames)
+L.attrsNamed(...propNames)
```

```diff
-L.propsOf(object)
+L.attrsOf(object)
```

## 14.0.0

*The current plan is to change Partial Lenses to support so called naked or
Expand Down
132 changes: 118 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,15 @@ parts. [Try Lenses!](https://calmm-js.github.io/partial.lenses/playground.html)
* [`L.slice(maybeBegin, maybeEnd) ~> lens`](#L-slice "L.slice: Maybe Number -> Maybe Number -> PLens [a] [a]") <small><sup>v8.1.0</sup></small>
* [`L.suffix(maybeEnd) ~> lens`](#L-suffix "L.suffix: Maybe Number -> PLens [a] [a]") <small><sup>v11.12.0</sup></small>
* [Lensing objects](#lensing-objects)
* [`L.pickIn({prop: lens, ...props}) ~> lens`](#L-pickIn "L.pickIn: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") <small><sup>v11.11.0</sup></small>
* [`L.attrs({prop: lens, ...props}) ~> lens`](#L-attrs "L.attrs: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") <small><sup>v14.x.y</sup></small>
* [`L.attrsNamed(...propNames) ~> lens`](#L-attrsNamed "L.attrsNamed: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v14.x.y</sup></small>
* [`L.attrsOf(object) ~> lens`](#L-attrsOf "L.attrsOf: {p1: a1, ...ps} -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v14.x.y</sup></small>
* [`L.attrsOr(lens, {prop: lens, ...props}) ~> lens`](#L-attrsOr "L.attrsOr: PLens s a -> {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls, ...p: s} {p1: a1, ...pls, ...p: a}") <small><sup>v14.x.y</sup></small>
* [`L.object(lens) ~> lens`](#L-object "L.object: PLens s a -> PLens {...p: s} {...p: a}") <small><sup>v14.x.y</sup></small>
* ~~[`L.pickIn({prop: lens, ...props}) ~> lens`](#L-pickIn "L.pickIn: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") <small><sup>v11.11.0</sup></small>~~
* [`L.prop(propName) ~> lens`](#L-prop "L.prop: (p: a) -> PLens {p: a, ...ps} a") or `propName` <small><sup>v1.0.0</sup></small>
* [`L.props(...propNames) ~> lens`](#L-props "L.props: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v1.4.0</sup></small>
* [`L.propsOf(object) ~> lens`](#L-propsOf "L.propsOf: {p1: a1, ...ps} -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v11.13.0</sup></small>
* ~~[`L.props(...propNames) ~> lens`](#L-props "L.props: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v1.4.0</sup></small>~~
* ~~[`L.propsOf(object) ~> lens`](#L-propsOf "L.propsOf: {p1: a1, ...ps} -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v11.13.0</sup></small>~~
* [`L.removable(...propNames) ~> lens`](#L-removable "L.removable: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps, ...o}") <small><sup>v9.2.0</sup></small>
* [Lensing strings](#lensing-strings)
* [`L.matches(/.../) ~> lens`](#L-matches "L.matches: RegExp -> PLens String String") <small><sup>v10.4.0</sup></small>
Expand Down Expand Up @@ -1985,7 +1990,7 @@ See the [BST traversal](#bst-traversal) section for a more meaningful example.
`L.branchOr` creates a new traversal from a given traversal and a given possibly
nested template object. The template specifies how the new traversal should
visit the corresponding properties of an object. The separate traversal is used
for properties not defined in the template.
for properties not defined in the template. See also [`L.attrsOr`](#L-attrsOr).

For example:

Expand Down Expand Up @@ -3451,7 +3456,97 @@ When creating new objects, partial lenses generally ignore everything but own
string keys. In particular, properties from the prototype chain are not copied
and neither are properties with symbol keys.

##### <a id="L-pickIn"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-pickIn) [`L.pickIn({prop: lens, ...props}) ~> lens`](#L-pickIn "L.pickIn: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") <small><sup>v11.11.0</sup></small>
##### <a id="L-attrs"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-attrs) [`L.attrs({prop: lens, ...props}) ~> lens`](#L-attrs "L.attrs: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") <small><sup>v14.x.y</sup></small>

`L.attrs` creates a lens or isomorphism from a given possibly nested template of
lenses or isomorphisms to be used on the corresponding properties of an object
structure. Other properties in the manipulated object structure are ignored.

For example:

```js
L.get(
L.attrs({x: L.negate, y: L.add(1)}),
{x: 1, y: -2, z: 3}
)
// {x: -1, y: -1}
```

Note that `L.attrs` is equivalent to [`L.attrsOr(L.zero)`](#L-attrsOr).

##### <a id="L-attrsNamed"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-attrsNamed) [`L.attrsNamed(...propNames) ~> lens`](#L-attrsNamed "L.attrsNamed: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v14.x.y</sup></small>

`L.attrsNamed` focuses on a subset of properties of an object, allowing one to
treat the subset of properties as a unit. The view of `L.attrsNamed` is
`undefined` when the focus is not an object. Otherwise the view is an object
containing a subset of the properties. Setting through `L.attrsNamed` updates
the whole subset of properties, which means that any missing properties are
removed if they did exists previously. When set, any extra properties are
ignored.

```js
L.set(L.attrsNamed('x', 'y'), {x: 4}, {x: 1, y: 2, z: 3})
// { x: 4, z: 3 }
```

Note that `L.attrsNamed(k1, ..., kN)` is equivalent to [`L.attrs({[k1]: [], ...,
[kN]: []})`](#L-attrs).

##### <a id="L-attrsOf"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-attrsOf) [`L.attrsOf(object) ~> lens`](#L-attrsOf "L.attrsOf: {p1: a1, ...ps} -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v14.x.y</sup></small>

`L.attrsOf(o)` is shorthand for
[`L.attrsNamed(...Object.keys(o))`](#L-attrsNamed) allowing one to focus on the
properties specified via the given sample object.

##### <a id="L-attrsOr"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-attrsOr) [`L.attrsOr(lens, {prop: lens, ...props}) ~> lens`](#L-attrsOr "L.attrsOr: PLens s a -> {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls, ...p: s} {p1: a1, ...pls, ...p: a}") <small><sup>v14.x.y</sup></small>

`L.attrsOr` creates a lens or isomorphism from a given possibly nested template
of lenses or isomorphisms to be used on the corresponding properties of an
object structure and a default lens to be used on other properties. See also
[`L.branchOr`](#L-branchOr).

```js
L.getInverse(
L.attrsOr(L.identity, {x: L.negate, y: L.add(1)}),
{x: -1, y: -1}
)
// {x: 1, y: -2}
```

Note that [`L.attrs`](#L-attrs) is equivalent to `L.attrsOr(L.zero)` and
[`L.object(lens)`](#L-object) is equivalent to `L.attrsOr(lens, {})`.

##### <a id="L-object"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-object) [`L.object(lens) ~> lens`](#L-object "L.object: PLens s a -> PLens {...p: s} {...p: a}") <small><sup>v14.x.y</sup></small>

`L.object` lifts a lens or isomorphism between elements, `a ≅ b`, to a lens or
isomorphism between objects, `{...p: a} ≅ {...p: b}`. See also
[`L.array`](#L-array).

For example:

```js
L.get(
L.object(L.array(L.negate)),
{xs: [1, -2, 3], ys: [-1, 2, -3]}
)
// {xs: [-1, 2, -3], ys: [1, -2, 3]}
```

```js
L.set(
[L.object(L.array(L.negate)), 'xs', L.append],
4,
{xs: [1, -2, 3], ys: [-1, 2, -3]}
)
// {xs: [1, -2, 3, -4], ys: [-1, 2, -3]}
```

Note that `L.object(lens)` is equivalent to [`L.attrsOr(lens, {})`](#L-attrsOr).

##### <a id="L-pickIn"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-pickIn) ~~[`L.pickIn({prop: lens, ...props}) ~> lens`](#L-pickIn "L.pickIn: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") <small><sup>v11.11.0</sup></small>~~

**WARNING: `L.pickIn` has been obsoleted. Consider using [`L.attrs`](#L-attrs)
instead. See [CHANGELOG](./CHANGELOG.md#13130) for details.**

`L.pickIn` creates a lens from the given possibly nested object template of
lenses similar to [`L.pick`](#L-pick) except that the lenses in the template are
Expand Down Expand Up @@ -3499,7 +3594,11 @@ L.set([L.rewrite(objectTo(XYZ)), 'z'], 3, new XYZ(3, 1, 4))
// XYZ { x: 3, y: 1, z: 3 }
```

##### <a id="L-props"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-props) [`L.props(...propNames) ~> lens`](#L-props "L.props: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v1.4.0</sup></small>
##### <a id="L-props"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-props) ~~[`L.props(...propNames) ~> lens`](#L-props "L.props: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v1.4.0</sup></small>~~

**WARNING: `L.props` has been obsoleted. Consider using
[`L.attrsNamed`](#L-attrsNamed) instead. See [CHANGELOG](./CHANGELOG.md#13130)
for details.**

`L.props` focuses on a subset of properties of an object, allowing one to treat
the subset of properties as a unit. The view of `L.props` is `undefined` when
Expand All @@ -3517,7 +3616,11 @@ L.set(L.props('x', 'y'), {x: 4}, {x: 1, y: 2, z: 3})
Note that `L.props(k1, ..., kN)` is equivalent to [`L.pick({[k1]: k1, ..., [kN]:
kN})`](#L-pick) and [`L.pickIn({[k1]: [], ..., [kN]: []})`](#L-pickIn).

##### <a id="L-propsOf"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-propsOf) [`L.propsOf(object) ~> lens`](#L-propsOf "L.propsOf: {p1: a1, ...ps} -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v11.13.0</sup></small>
##### <a id="L-propsOf"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#L-propsOf) ~~[`L.propsOf(object) ~> lens`](#L-propsOf "L.propsOf: {p1: a1, ...ps} -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v11.13.0</sup></small>~~

**WARNING: `L.propsOf` has been obsoleted. Consider using
[`L.attrsOf`](#L-attrsOf) instead. See [CHANGELOG](./CHANGELOG.md#13130) for
details.**

`L.propsOf(o)` is shorthand for [`L.props(...Object.keys(o))`](#L-props)
allowing one to focus on the properties specified via the given sample object.
Expand Down Expand Up @@ -3775,7 +3878,7 @@ L.modify(

`L.array` lifts an isomorphism between elements, `a ≅ b`, to an isomorphism
between an [array-like](#array-like) object and an array of elements, `[a]
[b]`.
[b]`. See also [`L.object`](#L-object).

For example:

Expand Down Expand Up @@ -4237,14 +4340,15 @@ requirement is to implement it in the lenses that are used to access the
`maximum` and `initial` values. This way the UI components that allows the user
to edit those values can be dumb and do not need to know about the restrictions.

One way to build such a lens is to use a combination of [`L.props`](#L-props)
(or, in more complex cases, [`L.pick`](#L-pick)) to limit the set of properties
to deal with, and [`L.rewrite`](#L-rewrite) to insert the desired restriction
logic. Here is how it could look like for the `maximum`:
One way to build such a lens is to use a combination of
[`L.attrsNamed`](#L-attrsNamed) (or, in more complex cases, [`L.pick`](#L-pick))
to limit the set of properties to deal with, and [`L.rewrite`](#L-rewrite) to
insert the desired restriction logic. Here is how it could look like for the
`maximum`:

```js
const maximum = [
L.props('maximum', 'initial'),
L.attrsNamed('maximum', 'initial'),
L.rewrite(props => {
const {maximum, initial} = props
if (maximum < initial)
Expand Down Expand Up @@ -5312,7 +5416,7 @@ We can now remove the `extra` `fields` like this:
transform(
R.ifElse(
R.allPass([R.is(Object), R.complement(R.is(Array))]),
L.remove(L.props('extra', 'fields')),
L.remove(L.attrsNamed('extra', 'fields')),
R.identity
),
sampleBloated
Expand Down
5 changes: 4 additions & 1 deletion bench/bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,10 @@ R.forEach(
`L.get(abcM, {x: 1})`,
`L.get(abcS, {x: 1})`
],
[`L.set(L.props('x', 'y'), {x: 2, y: 3}, {x: 1, y: 2, z: 4})`],
[
`L.set(L.props('x', 'y'), {x: 2, y: 3}, {x: 1, y: 2, z: 4})`,
`L.set(L.attrsNamed('x', 'y'), {x: 2, y: 3}, {x: 1, y: 2, z: 4})`
],
[
`L.transform(
L.lazy(rec =>
Expand Down
Loading

0 comments on commit 95b00bf

Please sign in to comment.