diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22a313be..4f3a0954 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/README.md b/README.md
index d526581c..f81c6f9c 100644
--- a/README.md
+++ b/README.md
@@ -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]") v8.1.0
* [`L.suffix(maybeBegin) ~> lens`](#l-suffix "L.suffix: Maybe Number -> PLens [a] [a]") v11.12.0
* [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}") v11.11.0
- * [`L.prop(propName) ~> lens`](#l-prop "L.prop: (p: a) -> PLens {p: a, ...ps} a") or `propName` v1.0.0
- * [`L.props(...propNames) ~> lens`](#l-props "L.props: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v1.4.0
+ * [`L.attrs(...propNames) ~> lens`](#L-attrs "L.attrs: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v14.x.y
+ * [`L.attrsIn({prop: lens, ...props}) ~> lens`](#l-attrsin "L.attrsIn: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") v14.x.y
+ * [`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}") v14.x.y
+ * [`L.object(lens) ~> lens`](#l-object "L.object: PLens s a -> PLens {...p: s} {...p: a}") v14.x.y
+ * ~~[`L.pickIn({prop: lens, ...props}) ~> lens`](#l-pickin "L.pickIn: {p1: PLens s1 a1, ...pls} -> PLens {p1: s1, ...pls} {p1: a1, ...pls}") v11.11.0~~
+ * [`L.prop(propName) ~> lens`](#L-prop "L.prop: (p: a) -> PLens {p: a, ...ps} a") or `propName` v1.0.0
+ * ~~[`L.props(...propNames) ~> lens`](#l-props "L.props: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v1.4.0~~
* [`L.propsExcept(...propNames) ~> lens`](#l-propsexcept "L.propsExcept: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {...o}") v14.11.0
* ~~[`L.propsOf(object) ~> lens`](#l-propsof "L.propsOf: {p1: a1, ...ps} -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps}") v11.13.0~~
- * [`L.removable(...propNames) ~> lens`](#l-removable "L.removable: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps, ...o}") v9.2.0
+ * [`L.removable(...propNames) ~> lens`](#L-removable "L.removable: (p1: a1, ...ps) -> PLens {p1: a1, ...ps, ...o} {p1: a1, ...ps, ...o}") v9.2.0
* [Lensing strings](#lensing-strings)
* [`L.matches(/.../) ~> lens`](#l-matches "L.matches: RegExp -> PLens String String") v10.4.0
* [Providing defaults](#providing-defaults)
@@ -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:
@@ -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.
-##### [≡](#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}") v11.11.0
+##### [≡](#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}") v14.x.y
+
+`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).
+
+##### [≡](#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}") v14.x.y
+
+`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).
+
+##### [≡](#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}") v14.x.y
+
+`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, {})`.
+
+##### [≡](#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}") v14.x.y
+
+`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).
+
+##### [≡](#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}") v11.11.0~~
+
+**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
@@ -3736,7 +3825,11 @@ L.set([L.rewrite(objectTo(XYZ)), 'z'], 3, new XYZ(3, 1, 4))
// XYZ { x: 3, y: 1, z: 3 }
```
-##### [≡](#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}") v1.4.0
+##### [≡](#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}") v1.4.0~~
+
+**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
@@ -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:
@@ -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)
@@ -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
diff --git a/bench/bench.js b/bench/bench.js
index 94ecbf53..ea1f789a 100644
--- a/bench/bench.js
+++ b/bench/bench.js
@@ -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 =>
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index bb447f91..0adb6187 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -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
}
@@ -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 => {
@@ -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)
@@ -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)
@@ -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'
@@ -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)
diff --git a/test/tests.js b/test/tests.js
index e3bec5aa..54b1a829 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -243,6 +243,9 @@ describe('arities', () => {
attemptEveryDown: 1,
attemptEveryUp: 1,
attemptSomeDown: 1,
+ attrs: 0,
+ attrsIn: 1,
+ attrsInOr: 2,
branch: 1,
branchOr: 2,
branches: 0,
@@ -340,6 +343,7 @@ describe('arities', () => {
negate: 4,
none: 3,
normalize: 1,
+ object: 1,
offset: 2,
optional: 4,
or: 2,
@@ -1226,6 +1230,68 @@ describe('L.pickIn', () => {
}),
{meta: {file: './foo.txt', ext: 'txt'}}
)
+ testEq(() => L.remove(L.pickIn({x: L.negate}), {x: 1}), {})
+ testEq(() => L.get(L.pickIn({x: L.negate}), {}), undefined)
+})
+
+describe('L.attrsInOr', () => {
+ testEq(() => L.set(L.attrsInOr(L.zero, {}), {}, {x: 1}), {x: 1})
+ testEq(() => L.get(L.attrsInOr(L.zero, {}), {y: 1}), {})
+ testEq(() => L.get(L.attrsInOr(L.negate, {x: L.add(1)}), {x: 1, y: 2}), {
+ x: 2,
+ y: -2
+ })
+ testEq(
+ () => L.set([L.attrsInOr(L.negate, {x: L.add(1)}), 'x'], -2, {x: 1, y: 2}),
+ {x: -3, y: 2}
+ )
+ testEq(
+ () =>
+ L.set(
+ L.attrsInOr(L.negate, {x: L.add(1), z: L.subtract(1)}),
+ {x: -2, z: 1},
+ {x: 1, y: 2}
+ ),
+ {x: -3, z: 2}
+ )
+ testEq(
+ () => L.remove(L.attrsInOr(L.negate, {x: L.add(1)}), {x: 1, y: 2}),
+ undefined
+ )
+ testEq(
+ () => L.set(L.attrsInOr(L.negate, {x: L.add(1)}), {}, {x: 1, y: 2}),
+ {}
+ )
+ testEq(() => L.set(L.attrsInOr(L.defaults(0), {}), {x: 0}, {}), {})
+})
+
+describe('L.attrsIn', () => {
+ testEq(() => L.get(L.attrsIn({x: L.zero}), 'not an object'), undefined)
+ testEq(() => L.get(L.attrsIn({x: L.negate}), {}), {})
+ testEq(() => L.get(L.attrsIn({x: L.negate}), {y: 1}), {})
+ testEq(() => L.get(L.attrsIn({x: L.negate}), {x: 1, y: 2}), {x: -1})
+ testEq(() => L.set(L.attrsIn({x: L.negate}), {y: 1}, {y: 2}), {y: 2})
+ testEq(() => L.set(L.attrsIn({x: L.negate}), {}, {}), {})
+ testEq(() => L.remove(L.attrsIn({x: L.negate}), {x: 1}), undefined)
+ testEq(
+ () =>
+ L.get(L.attrsIn({meta: {file: [], ext: []}}), {
+ meta: {file: './foo.txt', base: 'foo', ext: 'txt'}
+ }),
+ {meta: {file: './foo.txt', ext: 'txt'}}
+ )
+})
+
+describe('L.object', () => {
+ testEq(() => L.get(L.object(L.filter(x => x < 0)), {x: [1], y: [1, -1]}), {
+ x: [],
+ y: [-1]
+ })
+ testEq(
+ () =>
+ L.set(L.object(L.filter(x => x < 0)), {y: [-2]}, {x: [1], y: [1, -1]}),
+ {x: [1], y: [-2, 1]}
+ )
})
describe('L.props', () => {
@@ -1247,6 +1313,46 @@ describe('L.props', () => {
])
})
+describe('L.attrs', () => {
+ testEq(() => L.remove(L.attrs('x', 'y'), {x: 1, y: 2, z: 3}), {z: 3})
+ testEq(() => L.set(L.attrs('x', 'y'), {}, {x: 1, y: 2, z: 3}), {z: 3})
+ testEq(() => L.set(L.attrs('x', 'y'), {y: 4}, {x: 1, y: 2, z: 3}), {
+ y: 4,
+ z: 3
+ })
+
+ for (const maybeInverse of [R.identity, L.inverse]) {
+ testEq(() => L.get(maybeInverse(L.attrs('x', 'y')), {x: 1, y: 2, z: 3}), {
+ x: 1,
+ y: 2
+ })
+ testEq(() => L.get(maybeInverse(L.attrs('x', 'y')), {z: 3}), {})
+ testEq(() => L.get(maybeInverse(L.attrs('x', 'y')), {x: 2, z: 3}), {
+ x: 2
+ })
+ testEq(
+ () => L.remove(maybeInverse(L.attrs('x', 'y')), {x: 1, y: 2}),
+ undefined
+ )
+ testEq(() => L.set(maybeInverse(L.attrs('x', 'y')), {}, {x: 1, y: 2}), {})
+ testEq(() => L.set(maybeInverse(L.attrs('x', 'y')), {y: 4}, {x: 1, y: 2}), {
+ y: 4
+ })
+ testEq(
+ () => L.remove(maybeInverse(L.attrs('x', 'y')), {x: 1, y: 2}),
+ undefined
+ )
+ testEq(() => L.set(maybeInverse(L.attrs('a', 'b')), {a: 2}, {a: 1, b: 3}), {
+ a: 2
+ })
+ testEq(
+ () =>
+ I.keys(L.get(maybeInverse(L.attrs('x', 'b', 'y')), {b: 1, y: 1, x: 1})),
+ ['x', 'b', 'y']
+ )
+ }
+})
+
describe('L.propsExcept', () => {
testEq(() => L.get(L.propsExcept('x', 'y'), {x: 1, y: 2}), undefined)
testEq(() => L.get(L.propsExcept('x', 'y'), {x: 1, z: 2}), {z: 2})
diff --git a/test/types.js b/test/types.js
index 87b9c6f2..fe5ceef5 100644
--- a/test/types.js
+++ b/test/types.js
@@ -380,6 +380,10 @@ export const suffix = T.fn([T_sliceIndex], T_lens)
// Lensing objects
+export const attrs = T.fnVarN(0, T.string, T_lens)
+export const attrsIn = T.fn([template(T_lens)], T_lens)
+export const attrsInOr = T.fn([T_lens, template(T_lens)], T_lens)
+export const object = T.fn([T_lens], T_lens)
export const pickIn = T.fn([template(T_lens)], T_lens)
export const prop = T.fn([T.string], T_lens)
export const props = T.fnVarN(0, T.string, T_lens)