Skip to content

Commit

Permalink
Callable lenses as getters
Browse files Browse the repository at this point in the history
  • Loading branch information
aynik committed Aug 16, 2021
1 parent d65b796 commit 94e2a64
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 33 deletions.
32 changes: 11 additions & 21 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ A type safe functional lens implemented via proxy
- [lens(root?: A): ProxyLens<A, A>](#lensaroot-a-proxylensa-a)
- [Getter<A, B>](#gettera-b-type)
- [Setter<A, B>](#settera-b-type)
- [Lens<A, B>](#lensa-b-type)
- [ProxyLens<A, B>](#proxylensa-b-type)
- [BaseLens<A, B>](#baselensa-b-interface)
+ [.get(a?: A): B](#geta-a-b)
+ [.set(b: B, a?: A): A](#setb-b-a-a-a)
+ [.let(b: B): ProxyLens<A, A>](#letb-b-proxylensa-a)
+ [.peg({ get }: Lens<A, B>): ProxyLens<A, A>](#peg-get--lensa-b-proxylensa-a)
+ [.mod<C>(get: Getter<B, C>, set?: Setter<B, C>): ProxyLens<A, C>](#modcget-getterb-c-set-setterb-c-proxylensa-c)
+ [.peg(get: Getter<A, B> | ProxyLens<A, B>): ProxyLens<A, A>](#pegget-gettera-b--proxylensa-b-proxylensa-a)
+ [.mod<C>(get: Getter<B, C> | ProxyLens<B, C>, set?: Setter<B, C>): ProxyLens<A, C>](#modcget-getterb-c--proxylensb-c-set-setterb-c-proxylensa-c)
- [ArrayLens<A, B>](#arraylensa-b-interface)
+ [.del(index: number, a?: A): ProxyLens<A, A>](#delindex-number-a-a-proxylensa-a)
+ [.put(index: number, b: B | B[], a?: A): ProxyLens<A, A>](#putindex-number-b-b--b-a-a-proxylensa-a)
Expand Down Expand Up @@ -57,7 +56,6 @@ import {
lens,
Getter,
Setter,
Lens,
ProxyLens,
BaseLens,
ArrayLens,
Expand Down Expand Up @@ -100,14 +98,6 @@ Any function that implements the signature `(b: B, a: A) => A`.

---

### `Lens<A, B>` (type)

Any object that implements the interface `{ get: Getter<A, B>; set: Setter<A, B> }`.

[Back to top ↑](#proxy-lens)

---

### `ProxyLens<A, B>` (type)

A union between the interfaces of `BaseLens<A, B>` and `ArrayLens<A, B>`.
Expand All @@ -123,8 +113,8 @@ type BaseLens<A, B> = {
get(target?: A): B
set(value: B, target?: A): A
let(value: B): ProxyLens<A, A>
peg({ get }: Lens<A, B>): ProxyLens<A, A>
mod<C>(get: Getter<B, C>, set?: Setter<B, C>): ProxyLens<A, C>
peg(get: Getter<A, B> | ProxyLens<A, B>): ProxyLens<A, A>
mod<C>(get: Getter<B, C> | ProxyLens<B, C>, set?: Setter<B, C>): ProxyLens<A, C>
}
```
Expand Down Expand Up @@ -186,9 +176,9 @@ lens<{ a: boolean, b: boolean }>()

---

#### `.peg({ get }: Lens<A, B>): ProxyLens<A, A>`
#### `.peg(get: Getter<A, B> | ProxyLens<A, B>): ProxyLens<A, A>`

Pegs a lens (or an object with just a getter) to another lens with the same signature. Like [`.let`](#letb-b-proxylensa-a) returns a lens focused on the root so other operations may be chained.
Pegs a getter or a lens to another lens with the same signature. Like [`.let`](#letb-b-proxylensa-a) returns a lens focused on the root so other operations may be chained.

```typescript
lens({ a: false, b: true })
Expand All @@ -212,9 +202,9 @@ lens<{ a: boolean, b: boolean }>()

---

#### `.mod<C>(get: Getter<B, C>, set?: Setter<B, C>): ProxyLens<A, C>`
#### `.mod<C>(get: Getter<B, C> | ProxyLens<B, C>, set?: Setter<B, C>): ProxyLens<A, C>`

It's a method used to build modifications between two types `A` and `C` through a common type `B`. It takes one getter and an optional setter in case we want it to be a two way transformation. It then returns a new lens from the previous root type `A` to the new target type `C`.
It's a method used to build modifications between two types `A` and `C` through a common type `B`. It takes either a lens or one getter and an optional setter in case we want it to be a two way transformation. It then returns a new lens from the previous root type `A` to the new target type `C`.

```typescript
lens({ a: { b: true }}).mod(
Expand Down Expand Up @@ -461,9 +451,9 @@ assert.deepEqual(localizedEmployedMary, {

---

### Pegging lenses with the [`.peg`](#peg-get--lensa-b-proxylensa-a) method
### Pegging lenses with the [`.peg`](#pegget-gettera-b--proxylensa-b-proxylensa-a) method

Sometimes we want a lens to depend on other lens, an easy way to do this is to use the [`.peg`](#peg-get--lensa-b-proxylensa-a) method, where we can pass another lens so the value of the first is derived from the second.
Sometimes we want a lens to depend on other lens, an easy way to do this is to use the [`.peg`](#pegget-gettera-b--proxylensa-b-proxylensa-a) method, where we can pass another lens so the value of the first is derived from the second.

```typescript
const selfEmployedJohn = lens(john)
Expand All @@ -482,7 +472,7 @@ Please note that we used a one-way mod to append a string to John's name.

---

### Modifying lenses with the [`.mod`](#modcget-getterb-c-set-setterb-c-proxylensa-c) method
### Modifying lenses with the [`.mod`](#modcget-getterb-c--proxylensb-c-set-setterb-c-proxylensa-c) method

This method can be used to create modifications between two types. For example, we can create a two-way transform between objects and strings.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "proxy-lens",
"version": "1.0.0",
"version": "1.1.0",
"description": "A type safe functional lens implemented via proxy",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
20 changes: 18 additions & 2 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ describe('get', () => {
true,
)
})

it('gets a nested value using callable lens', () => {
const source: { a: { b: { c: boolean } } } = { a: { b: { c: true } } }
expect(lens(source).a.b.c()).toEqual(true)
})

it('gets a nested value using abstract callable lens', () => {
const source = { a: { b: { c: true } } }
expect(lens<{ a: { b: { c: boolean } } }>().a.b.c(source)).toEqual(true)
})
})

describe('set', () => {
Expand Down Expand Up @@ -238,7 +248,11 @@ describe('peg', () => {
}
expect(
lens(source)
.a.c.peg(lens<typeof source>().b.mod((bool) => !bool))
.a.c.peg(
lens<typeof source>().b.mod<boolean>(
(bool: boolean): boolean => !bool,
),
)
.a.c.get(),
).toEqual(false)
})
Expand Down Expand Up @@ -275,7 +289,9 @@ describe('peg', () => {
expect(
lens<{ a: { c: boolean }; b: boolean }>()
.a.c.peg(
lens<{ a: { c: boolean }; b: boolean }>().b.mod((bool) => !bool),
lens<{ a: { c: boolean }; b: boolean }>().b.mod<boolean>(
(bool: boolean): boolean => !bool,
),
)
.a.c.get(source),
).toEqual(false)
Expand Down
27 changes: 18 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
export type Getter<A, B> = (a: A) => B
export type Setter<A, B> = (b: B, a: A) => A
export type Lens<A, B> = { get: Getter<A, B>; set: Setter<A, B> }

export type BaseLens<A, B> = {
export type BaseLens<A, B> = ((target?: A) => B) & {
get(target?: A): B
set(value: B, target?: A): A
let(value: B): ProxyLens<A, A>
peg({ get }: Lens<A, B>): ProxyLens<A, A>
mod<C>(get: Getter<B, C>, set?: Setter<B, C>): ProxyLens<A, C>
peg(get: Getter<A, B> | ProxyLens<A, B>): ProxyLens<A, A>
mod<C>(
get: Getter<B, C> | ProxyLens<B, C>,
set?: Setter<B, C>,
): ProxyLens<A, C>
}

type ExtractRecord<T> = Extract<T, { [key: string]: unknown }>
Expand Down Expand Up @@ -52,16 +54,21 @@ function baseLens<A, B>(
get: (target: A): B => get((target ?? root) as A),
set: (value: B, target: A): A => set(value, (target ?? root) as A),
let: (value: B): ProxyLens<A, A> =>
lens<A>(root).mod<A>(
proxyLens<A, A>(
(target: A): A => set(value, target),
(value: A): A => value,
root,
),
peg: ({ get: get_ }: Lens<A, B>): ProxyLens<A, A> =>
lens<A>(root).mod<A>(
peg: (get_: Getter<A, B> | ProxyLens<A, B>): ProxyLens<A, A> =>
proxyLens<A, A>(
(target: A): A => set(get_(set(get(target), target)), target),
(value: A): A => set(get_(set(get(value), value)), value),
root,
),
mod: <C>(get_: Getter<B, C>, set_?: Setter<B, C>): ProxyLens<A, C> =>
mod: <C>(
get_: Getter<B, C> | ProxyLens<B, C>,
set_?: Setter<B, C>,
): ProxyLens<A, C> =>
proxyLens<A, C>(
(target: A): C => get_(get(target)),
(value: C, target: A): A =>
Expand Down Expand Up @@ -124,7 +131,9 @@ export function proxyLens<A, B>(
set: Setter<A, B> | Setter<A, ArrayFrom<B>>,
root?: A,
): ProxyLens<A, B> {
return new Proxy({} as ProxyLens<A, B>, {
const callable = (target?: A): B | ArrayFrom<B> => get((target ?? root) as A)
return new Proxy(callable as ProxyLens<A, B>, {
apply: (_, __, [target]: [A?]) => callable(target),
get: (_, key) =>
arrayLens(
get as Getter<A, ArrayFrom<B>>,
Expand Down

0 comments on commit 94e2a64

Please sign in to comment.