Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added attrsOr, attrs, and object #173

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Partial Lenses Changelog

## 14.x.y

Obsoleted `L.pickIn`, and `L.props`. They are based on `L.pick`,
which uses lenses and requires super linear time when written through. The new
`L.attrsIn`, and `L.attrs` 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.attrsIn` and `L.props` with `L.attrs`:

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

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

## 14.14.0

Renamed `L.append` to `L.appendTo`. This means that `L.append` is deprecated.
Expand Down
111 changes: 102 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,13 @@ 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.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.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>
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,92 @@ 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.attrsInOr(lens,
{})`](#l-attrsinor).

##### <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 +3826,10 @@ 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.attrs`](#l-attrs)
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,14 @@ 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)
One way to build such a lens is to use a combination of [`L.attrs`](#l-attrs)
(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.attrs('maximum', 'initial'),
L.rewrite(props => {
const {maximum, initial} = props
if (maximum < initial)
Expand Down Expand Up @@ -6034,7 +6127,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.attrs('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
141 changes: 117 additions & 24 deletions src/partial.lenses.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,7 @@ const getPick = (process.env.NODE_ENV === 'production' ? id : C.res(I.freeze))(
for (const k in template) {
const t = template[k]
const v = I.isObject(t) ? getPick(t, x) : getAsU(id, t, x)
if (void 0 !== v) {
if (!r) r = {}
r[k] = v
}
if (void 0 !== v) (r ? r : (r = {}))[k] = v
}
return r
}
Expand Down Expand Up @@ -569,6 +566,26 @@ const identity = (x, i, _F, xi2yF) => xi2yF(x, i)

//

function fromNested(fromLevel, otherwise, template) {
const k2o = I.create(null)
for (const k in template) {
const v = template[k]
k2o[k] = I.isObject(v) ? fromNested(fromLevel, otherwise, v) : toFunction(v)
}
return fromLevel(otherwise, k2o)
}

const fromNestedOr = fromLevel =>
I.curryN(
2,
otherwise => (
(otherwise = toFunction(otherwise)),
template => fromNested(fromLevel, otherwise, template)
)
)

//

const branchAssemble = (process.env.NODE_ENV === 'production'
? id
: C.res(C.res(I.freeze)))(ks => xs => {
Expand Down Expand Up @@ -660,14 +677,66 @@ const branchOr1Level = (otherwise, k2o) => (x, _i, A, xi2yA) => {
}
}

function branchOrU(otherwise, template) {
const k2o = I.create(null)
for (const k in template) {
const v = template[k]
k2o[k] = I.isObject(v) ? branchOrU(otherwise, v) : toFunction(v)
//

const getAttrs = (process.env.NODE_ENV === 'production'
? I.id
: C.res(I.freeze))((otherwise, template, x) => {
if (x instanceof Object) {
const o = {}
for (const k in template) {
const v = template[k](x[k], k, Select, I.id)
if (void 0 !== v) o[k] = v
}
if (zero !== otherwise) {
x = toObject(x)
for (const k in x) {
if (void 0 === template[k]) {
const v = otherwise(x[k], k, Select, I.id)
if (void 0 !== v) o[k] = v
}
}
}
return o
}
return branchOr1Level(otherwise, k2o)
}
})

const setAttrs = (process.env.NODE_ENV === 'production'
? I.id
: C.res(I.freeze))((otherwise, template, y, x) => {
let p
y = y instanceof Object ? toObject(y) : I.protoless0
x = x instanceof Object ? toObject(x) : I.protoless0
let k
const getY = () => y[k]
for (k in template) {
const v = template[k](x[k], k, I.Identity, getY)
if (void 0 !== v) (p ? p : (p = {}))[k] = v
}
for (k in x) {
if (void 0 === template[k]) {
const v = otherwise(x[k], k, I.Identity, getY)
if (void 0 !== v) (p ? p : (p = {}))[k] = v
}
}
if (zero !== otherwise) {
for (k in y) {
if (void 0 === template[k] && void 0 === x[k]) {
const v = otherwise(void 0, k, I.Identity, getY)
if (void 0 !== v) (p ? p : (p = {}))[k] = v
}
}
}
return void 0 !== p ? p : y === I.protoless0 ? void 0 : I.object0
})

const attrsOr1Level = (otherwise, template) => (x, i, F, xi2yF) =>
F.map(
y => setAttrs(otherwise, template, y, x),
xi2yF(getAttrs(otherwise, template, x), i)
)

//

const replaced = (inn, out, x) => (I.acyclicEqualsU(x, inn) ? out : x)

Expand Down Expand Up @@ -1610,14 +1679,7 @@ export const seq = (process.env.NODE_ENV === 'production'

export const branchOr = (process.env.NODE_ENV === 'production'
? id
: C.par(1, C.ef(reqTemplate('branchOr'))))(
I.curryN(2, function branchOr(otherwise) {
otherwise = toFunction(otherwise)
return function branchOr(template) {
return branchOrU(otherwise, template)
}
})
)
: C.par(1, C.ef(reqTemplate('branchOr'))))(fromNestedOr(branchOr1Level))

export const branch = branchOr(zero)

Expand Down Expand Up @@ -2120,8 +2182,30 @@ export const suffix = n => slice(0 === n ? Infinity : !n ? 0 : -n, void 0)

// Lensing objects

export const pickIn = t =>
I.isObject(t) ? pick(modify(values, pickInAux, t)) : t
export const attrsInOr = (process.env.NODE_ENV === 'production'
? I.id
: C.par(1, C.ef(reqTemplate('attrsOr'))))(fromNestedOr(attrsOr1Level))

export const attrsIn = attrsInOr(zero)

export function attrs() {
const n = arguments.length
const template = {}
for (let i = 0; i < n; ++i) template[arguments[i]] = identity
return attrsIn(template)
}

export const object = lens => attrsInOr(lens, I.object0)

export const pickIn = (process.env.NODE_ENV === 'production'
? I.id
: fn => t => {
warn(
pickIn,
'`pickIn` has been obsoleted. Consider using `attrsIn` instead. See CHANGELOG for details.'
)
return fn(t)
})(t => (I.isObject(t) ? pick(modify(values, pickInAux, t)) : t))

export const prop =
process.env.NODE_ENV === 'production'
Expand All @@ -2131,12 +2215,21 @@ export const prop =
return x
}

export function props() {
const n = arguments[I.LENGTH]
export const props = (process.env.NODE_ENV === 'production'
? I.id
: fn =>
function() {
warn(
props,
'`props` has been obsoleted. Consider using `attrs` instead. See CHANGELOG for details.'
)
return fn.apply(null, arguments)
})(function() {
const n = arguments.length
const template = {}
for (let i = 0, k; i < n; ++i) template[(k = arguments[i])] = k
return pick(template)
}
})

export function propsExcept() {
const setish = I.create(null)
Expand Down
Loading