Skip to content

Commit

Permalink
Added attrs, attrsIn, attrsInOr, and object
Browse files Browse the repository at this point in the history
  • Loading branch information
polytypic committed Nov 4, 2018
1 parent a5a205b commit b777788
Show file tree
Hide file tree
Showing 6 changed files with 364 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.attrsIn`, `L.attrs`, 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.attrsIn(template)
```

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

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

## 14.14.0

Renamed `L.append` to `L.appendTo`. This means that `L.append` is deprecated.
Expand Down
122 changes: 108 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,16 @@ 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(maybeBegin) ~> 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.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.attrs(...propNames) ~> lens`](#L-attrs "L.attrs: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v14.x.y</sup></small>
* [`L.attrsIn({prop: lens, ...props}) ~> lens`](#l-attrsin "L.attrsIn: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") <small><sup>v14.x.y</sup></small>
* [`L.attrsInOr(lens, {prop: lens, ...props}) ~> lens`](#l-attrsinor "L.attrsInOr: 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.propsExcept(...propNames) ~> lens`](#l-propsexcept "L.propsExcept: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {...o}") <small><sup>v14.11.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>
* [`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>
* [Providing defaults](#providing-defaults)
Expand Down Expand Up @@ -2106,7 +2110,8 @@ 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.attrsInOr`](#l-attrsinor).

For example:

Expand Down Expand Up @@ -3688,7 +3693,91 @@ 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(...propNames) ~> lens`](#L-attrs "L.attrs: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") <small><sup>v14.x.y</sup></small>

`L.attrs` focuses on a subset of properties of an object, allowing one to treat
the subset of properties as a unit. The view of `L.attrs` is `undefined` when
the focus is not an object. Otherwise the view is an object containing a subset
of the properties. Setting through `L.attrs` 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.attrs('x', 'y'), {x: 4}, {x: 1, y: 2, z: 3})
// { x: 4, z: 3 }
```

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

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

`L.attrsIn` 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.attrsIn({x: L.negate, y: L.add(1)}),
{x: 1, y: -2, z: 3}
)
// {x: -1, y: -1}
```

Note that `L.attrsIn` is equivalent to [`L.attrsInOr(L.zero)`](#l-attrsinor).

##### <a id="l-attrsinor"></a> [](#contents) [](https://calmm-js.github.io/partial.lenses/index.html#l-attrsinor) [`L.attrsInOr(lens, {prop: lens, ...props}) ~> lens`](#l-attrsinor "L.attrsInOr: 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.attrsInOr` 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.attrsInOr(L.identity, {x: L.negate, y: L.add(1)}),
{x: -1, y: -1}
)
// {x: 1, y: -2}
```

Note that [`L.attrsIn`](#l-attrsin) is equivalent to `L.attrsInOr(L.zero)` and
[`L.object(lens)`](#l-object) is equivalent to `L.attrsInOr(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 @@ -3736,7 +3825,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 Down Expand Up @@ -4388,7 +4481,7 @@ also [`L.pattern`](#l-pattern).

`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 @@ -4959,14 +5052,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 @@ -6034,7 +6128,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 @@ -646,7 +646,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 b777788

Please sign in to comment.