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

Experiment to implicitly convert functions to getters #51

Merged
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 9.3.0

Obsoleted `L.to` and `L.just`. You can now directly compose optics with
ordinary functions (whose arity is not 4) and the result is a read-only optic.
This makes `L.to` the same as `R.identity` and `L.just` is the same as
`R.always`.

## 9.1.0

Obsoleted `L.mergeAs` and `L.merge`. `L.concatAs` and `L.concat` are now just
Expand Down
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ parts. [▶ Try Lenses!](https://calmm-js.github.io/partial.lenses/)
* [Adapting to data](#adapting-to-data)
* [`L.orElse(backupLens, primaryLens) ~> lens`](#L-orElse "L.orElse: (PLens s a, PLens s a) -> PLens s a")
* [Read-only mapping](#read-only-mapping)
* [`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")
* [`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")
* ~~[`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")~~
* ~~[`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")~~
* [Transforming data](#transforming-data)
* [`L.pick({prop: lens, ...props}) ~> lens`](#L-pick "L.pick: {p1: PLens s a1, ...pls} -> PLens s {p1: a1, ...pls}")
* [`L.replace(maybeValueIn, maybeValueOut) ~> lens`](#L-replace "L.replace: Maybe s -> Maybe s -> PLens s s")
Expand Down Expand Up @@ -481,8 +481,8 @@ to [`L.modify(lens, R.always(maybeValue), maybeData)`](#L-modify).

##### <a name="L-compose"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-compose) [`L.compose(...optics) ~> optic`](#L-compose "L.compose: (POptic s s1, ...POptic sN a) -> POptic s a") or `[...optics]`

`L.compose` performs composition of optics. The following equations
characterize composition:
`L.compose` performs composition of optics and ordinary functions. The
following equations characterize composition:

```jsx
L.compose() = L.identity
Expand Down Expand Up @@ -513,6 +513,20 @@ L.get(["a", 1], {a: ["b", "c"]})
// 'c'
```

You can also directly compose optics with ordinary functions (with max arity of
2). The result of such a composition is a read-only optic.

For example:

```js
L.get(["x", x => x + 1], {x: 1})
// 2
```
```js
L.set(["x", x => x + 1], 3, {x: 1})
// { x: 1 }
```

Note that [`R.compose`](http://ramdajs.com/docs/#compose) is not the same as
`L.compose`.

Expand All @@ -529,9 +543,9 @@ L.compose(optic, L.choose((maybeValue, index) =>
: toOptic(maybeValue, index)))
```

Note that with the [`L.just`](#L-just), `L.chain`, [`L.choice`](#L-choice)
and [`L.zero`](#L-zero) combinators, one can consider optics as subsuming the
maybe monad.
Note that with the [`R.always`](http://ramdajs.com/docs/#always),
`L.chain`, [`L.choice`](#L-choice) and [`L.zero`](#L-zero) combinators, one can
consider optics as subsuming the maybe monad.

##### <a name="L-choice"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-choice) [`L.choice(...lenses) ~> optic`](#L-choice "L.choice: (...PLens s a) -> POptic s a")

Expand Down Expand Up @@ -1581,7 +1595,9 @@ for [`L.choice`](#L-choice), for example.

#### Read-only mapping

##### <a name="L-just"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-just) [`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")
##### <a name="L-just"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-just) ~~[`L.just(maybeValue) ~> lens`](#L-just "L.just: Maybe a -> PLens s a")~~

**WARNING: `L.just` is obsolete, just use e.g. [`R.always`](http://ramdajs.com/docs/#always).**

`L.just` returns a read-only lens whose view is always the given value. In
other words, for all `x`, `y` and `z`:
Expand All @@ -1596,7 +1612,9 @@ Note that `L.just(x)` is equivalent to [`L.to(R.always(x))`](#L-to).
`L.just` can be seen as the unit function of the monad formed
with [`L.chain`](#L-chain).

##### <a name="L-to"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-to) [`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")
##### <a name="L-to"></a> [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses/#L-to) ~~[`L.to((maybeValue, index) => maybeValue) ~> lens`](#L-to "L.to: ((a, Index) -> b) -> PLens a b")~~

**WARNING: `L.to` is obsolete, you can directly compose plain functions with optics.**

`L.to` creates a read-only lens whose view is determined by the given function.

Expand Down
46 changes: 31 additions & 15 deletions src/partial.lenses.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function errorGiven(m, o) {
}

function reqFunction(o) {
if (!(isFunction(o) && o.length === 4))
if (!(isFunction(o) && (o.length === 4 || o.length <= 2)))
errorGiven(expectedOptic, o)
}

Expand Down Expand Up @@ -253,7 +253,7 @@ function setU(o, x, s) {
default:
if (process.env.NODE_ENV !== "production")
reqFunction(o)
return o(Ident, always(x), s, void 0)
return o.length === 4 ? o(Ident, always(x), s, void 0) : s
}
}

Expand All @@ -276,7 +276,7 @@ function getU(l, s) {
default:
if (process.env.NODE_ENV !== "production")
reqFunction(l)
return l(Const, id, s, void 0)
return l.length === 4 ? l(Const, id, s, void 0) : l(s, void 0)
}
}

Expand Down Expand Up @@ -403,6 +403,9 @@ function partitionIntoIndex(xi2b, xs, ts, fs) {
(xi2b(x = xs[i], i) ? ts : fs).push(x)
}

const fromReader = wi2x => (F, xi2yF, w, i) =>
(0,F.map)(always(w), xi2yF(wi2x(w, i), i))

//

export function toFunction(o) {
Expand All @@ -411,14 +414,14 @@ export function toFunction(o) {
return funProp(o)
case "number":
return funIndex(o)
case "function":
if (process.env.NODE_ENV !== "production")
reqFunction(o)
return o
default:
case "object":
if (process.env.NODE_ENV !== "production")
reqArray(o)
return composed(0, o)
default:
if (process.env.NODE_ENV !== "production")
reqFunction(o)
return o.length === 4 ? o : fromReader(o)
}
}

Expand All @@ -430,12 +433,14 @@ export const modify = curry((o, xi2x, s) => {
return setProp(o, xi2x(getProp(o, s), o), s)
case "number":
return setIndex(o, xi2x(getIndex(o, s), o), s)
case "function":
case "object":
return modifyComposed(o, xi2x, s)
default:
if (process.env.NODE_ENV !== "production")
reqFunction(o)
return o(Ident, xi2x, s, void 0)
default:
return modifyComposed(o, xi2x, s)
return o.length === 4
? o(Ident, xi2x, s, void 0)
: (xi2x(o(s, void 0), void 0), s)
}
})

Expand Down Expand Up @@ -750,10 +755,21 @@ export const orElse =

// Read-only mapping

export const to = wi2x => (F, xi2yF, w, i) =>
(0,F.map)(always(w), xi2yF(wi2x(w, i), i))
export const to = process.env.NODE_ENV === "production" ? id : wi2x => {
if (!to.warned) {
to.warned = 1
console.warn("partial.lenses: `to` is obsolete, you can directly compose plain functions with optics")
}
return wi2x
}

export const just = x => to(always(x))
export const just = process.env.NODE_ENV === "production" ? always : x => {
if (!just.warned) {
just.warned = 1
console.warn("partial.lenses: `just` is obsolete, just use e.g. `R.always`")
}
return always(x)
}

// Transforming data

Expand Down
4 changes: 2 additions & 2 deletions test/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ export const valueOr = v =>
export const orElse = R.curry((d, l) =>
choose(x => isDefined(get(l, x)) ? l : d))

export const just = v => to(R.always(v))
export const to = a2b => lens(a2b, (_, s) => s)
export const just = R.always
export const to = R.identity

export const pick = L.pick
export const replace = R.curry((i, o) =>
Expand Down
14 changes: 9 additions & 5 deletions test/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,17 @@ describe("L.zero", () => {
})

describe("L.to", () => {
testEq('L.get([0, "x", L.to(R.negate)], [{x:-1}])', 1)
testEq('L.set([0, "x", L.to(R.negate)], 2, [{x:-1}])', [{x:-1}])
testEq('L.get(L.to(x => x+1), 2)', 3)
testEq('L.get(x => x+1, 2)', 3)
testEq('L.modify(R.inc, R.negate, 1)', 1)
testEq('L.get([0, "x", R.negate], [{x:-1}])', 1)
testEq('L.set([0, "x", R.negate], 2, [{x:-1}])', [{x:-1}])
})

describe("L.just", () => {
testEq('L.get(L.just("always"), "anything")', "always")
testEq('L.set(L.just("always"), "anything", "original")', "original")
testEq('L.get(R.always("always"), "anything")', "always")
testEq('L.set(R.always("always"), "anything", "original")', "original")
})

describe("L.chain", () => {
Expand Down Expand Up @@ -613,8 +617,6 @@ if (process.env.NODE_ENV !== "production") {

testThrows('X.get(L.elems, [])')

testThrows('X.get(x => x, 0)')

testThrows('L.set(L.props("length"), "lol", undefined)')
testThrows('L.set(L.slice(undefined, undefined), 11, [])')
testThrows('L.pick(new XYZ(1,2,3))')
Expand All @@ -623,6 +625,8 @@ if (process.env.NODE_ENV !== "production") {
testThrows('L.set(L.augment({y: () => 1}), 45, {x: 1})')

testThrows('L.set(null, 1, 2)')

testThrows('L.toFunction((one, too, many) => 1)')
})
}

Expand Down
2 changes: 1 addition & 1 deletion test/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const object = template => object => {
}

export const fn = (argTys, resultTy) => fn => {
if (typeof fn !== "function")
if (typeof fn !== "function" || argTys.length < fn.length)
throw new Error(`fn(${argTys}, ${resultTy}): ${fn}`)
return R.curryN(argTys.length, function (...argIns) {
if (argTys.length !== argIns.length)
Expand Down
5 changes: 3 additions & 2 deletions test/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const T_opticOf = Category => T.lazy(T_optic => T.or(
T.integer,
T.string,
T.array(T_optic),
T.fn([T_maybeData, T_index], T_maybeData),
T_opticFnOf(Category)))

const T_optic = T_opticOf(T.or(T_applicative, T_functor))
Expand Down Expand Up @@ -127,8 +128,8 @@ export const valueOr = T.fn([T.any], T_lens)

export const orElse = T.fn([T_lens, T_lens], T_lens)

export const just = T.fn([T_maybeData], T_lens)
export const to = T.fn([T.fn([T_maybeData, T_index], T_maybeData)], T_lens)
export const just = T.fn([T.any], T.fnVar(T.any, T.any))
export const to = T.fn([T.any], T.any)

export const pick = T.fn([T.props(T_lens)], T_lens)
export const replace = T.fn([T_maybeData, T_maybeData], T_lens)
Expand Down