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)