Skip to content

Commit

Permalink
Add map and tap
Browse files Browse the repository at this point in the history
  • Loading branch information
aynik committed Aug 28, 2021
1 parent fa038fe commit a2ff948
Show file tree
Hide file tree
Showing 5 changed files with 474 additions and 10 deletions.
136 changes: 136 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A type safe functional lens implemented via proxy
- [Getter<A, B>](#gettera-b-type)
- [Setter<A, B>](#settera-b-type)
- [ProxyLens<A, B>](#proxylensa-b-type)
- [ProxyTraversal<A, B>](#proxytraversala-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)
Expand All @@ -25,6 +26,8 @@ A type safe functional lens implemented via proxy
- [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)
+ [.map(get: Getter<B, B>): ProxyLens<A, A>](#mapget-getterb-b-proxylensa-a)
+ [.tap(get?: Getter<B, boolean>): ProxyTraversal<A, B>](#tapget-getterb-boolean-proxytraversala-b)
* [Usage](#usage)
- [Setup](#setup)
- [Setting values with the `.set` method](#setting-values-with-the-set-method)
Expand All @@ -33,6 +36,8 @@ A type safe functional lens implemented via proxy
- [Pegging lenses with the `.peg` method](#pegging-lenses-with-the-peg-method)
- [Modifying lenses with the `.mod` method](#modifying-lenses-with-the-mod-method)
- [Manipulating immutable arrays with the `.del` and `.put` methods](#manipulating-immutable-arrays-with-the-del-and-put-methods)
- [Modifying array items with the `.map` method](#modifying-array-items-with-the-map-method)
- [Traversing arrays using the `.tap` method](#traversing-arrays-using-the-tap-method)
- [Using abstract lenses](#using-abstract-lenses)
- [Recursive abstract lenses](#recursive-abstract-lenses)

Expand Down Expand Up @@ -107,6 +112,14 @@ A union between the interfaces of `BaseLens<A, B>` and `ArrayLens<A, B>`.

---

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

A special kind of [`ProxyLens`](#proxylensa-b-type) used to represent traversals.

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

---

### `BaseLens<A, B>` (interface)

```typescript
Expand Down Expand Up @@ -315,6 +328,68 @@ lens<{ a: string[] }>()

---

#### `.map(get: Getter<B, B>): ProxyLens<A, A>`

It works like the [`.mod`](#modget-getterb-b-proxylensa-a) method but for array items.

```typescript
lens({ a: ['map'] })
.a.map((str) => str.toUpperCase()).get() // :: { a: ['MAP'] }

lens<{ a: string[] }>()
.a.map((str) => str.toUpperCase())
.get({ a: ['map'] }) // :: { a: ['MAP'] }
```

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

---

#### `.tap(get?: Getter<B, boolean>): ProxyTraversal<A, B>`

It's used to create traversal lenses from arrays, traversal lenses work like regular lenses but they are focused on the collection of values of a given property. It takes an optional getter that returns a boolean which can be used to filter the collection of values.

```typescript
lens({ a: [{ b: 'traversal'}, { b: 'lens' }] })
.a.tap().b.get() // :: ['traversal', 'lens']

lens<{ a: { b: string }[] }>()
.a.tap()
.b.get({ a: [{ b: 'traversal'}, { b: 'lens' }] }) // :: ['traversal', 'lens']

lens({ a: [{ b: 'traversal'}, { b: 'lens' }] })
.a.tap(({ b }) => b[0] === 'l').b.get() // :: ['lens']

lens<{ a: { b: string }[] }>()
.a.tap(({ b }) => b[0] === 'l').b.get() // :: ['lens']
.b.get({ a: [{ b: 'traversal'}, { b: 'lens' }] }) // :: ['lens']

lens({ a: [{ b: 'traversal'}, { b: 'lens' }] })
.a.tap()
.b.set(['modified', 'value']) // :: { a: [ { b: 'modified' }, { b: 'value' } ] }

lens({ a: { b: string }[] })
.a.tap().b.set(
['modified', 'value'],
{ a: [{ b: 'traversal'}, { b: 'lens' }] }
) // :: { a: [ { b: 'modified' }, { b: 'value' } ] }

lens({ a: [{ b: 'traversal'}, { b: 'lens' }] })
.a.tap(({ b }) => b[0] === 'l')
.b.set(['modified']) // :: { a: [ { b: 'traversal' }, { b: 'modified' } ] }

lens({ a: { b: string }[] })
.a.tap(({ b }) => b[0] === 'l')
.b.set(
['modified'],
{ a: [{ b: 'traversal'}, { b: 'lens' }] }
) // :: { a: [ { b: 'traversal' }, { b: 'modified' } ] }
```

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

---

## Usage

Here's a throughout usage example.
Expand Down Expand Up @@ -582,6 +657,67 @@ assert.deepEqual(sailorMary, {

---

### Modifying array items with the `.map` method

With this method we can map arrays against a modification getter.

```typescript
const people = [john, michael, mary]

const upperCaseNamePeople = lens(people)
.map(({ name, ...person }) => ({
...person,
name: name.toUpperCase(),
}))
.get()

assert.deepEqual(upperCaseNamePeople, [
{ name: 'JOHN WALLACE' },
{ name: 'MICHAEL COLLINS' },
{ name: 'MARY SANCHEZ' },
])
```


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

---

### Traversing arrays using the `.tap` method

Often times we want to work with a given array item property, for this we use traversal lenses which can be created this way.

```typescript
const peopleNames = lens(people).tap().name.get()

assert.deepEqual(peopleNames, [
'John Wallace',
'Michael Collins',
'Mary Sanchez',
])

const peopleNamesStartingWithM = lens(people)
.tap(({ name }) => name[0] === 'M')
.name.get()

assert.deepEqual(peopleNamesStartingWithM, ['Michael Collins', 'Mary Sanchez'])

const surnameFirstPeople = lens(people)
.tap()
.name.map((name: string) => name.split(' ').reverse().join(', '))
.get()

assert.deepEqual(surnameFirstPeople, [
{ name: 'Wallace, John' },
{ name: 'Collins, Michael' },
{ name: 'Sanchez, Mary' },
])
```

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

---

### Using abstract lenses

We can also use the lens methods in an abstract way, so we can pass it to higher order functions:
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "proxy-lens",
"version": "1.3.1",
"version": "1.4.0",
"description": "A type safe functional lens implemented via proxy",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"lint": "eslint './src/**/*.ts'",
"test": "jest",
"test:watch": "jest --watchAll",
"typecheck": "tsc --project tsconfig.json --pretty --noEmit && true",
"build": "tsc",
"watch": "tsc -w"
},
Expand Down
44 changes: 44 additions & 0 deletions src/example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,50 @@ assert.deepEqual(sailorMary, {
],
})

// Modifying array items with the `.map()` method

const people = [john, michael, mary]

const upperCaseNamePeople = lens(people)
.map(({ name, ...person }) => ({
...person,
name: name.toUpperCase(),
}))
.get()

assert.deepEqual(upperCaseNamePeople, [
{ name: 'JOHN WALLACE' },
{ name: 'MICHAEL COLLINS' },
{ name: 'MARY SANCHEZ' },
])

// Traversing arrays using the `.tap()` method

const peopleNames = lens(people).tap().name.get()

assert.deepEqual(peopleNames, [
'John Wallace',
'Michael Collins',
'Mary Sanchez',
])

const peopleNamesStartingWithM = lens(people)
.tap(({ name }) => name[0] === 'M')
.name.get()

assert.deepEqual(peopleNamesStartingWithM, ['Michael Collins', 'Mary Sanchez'])

const surnameFirstPeople = lens(people)
.tap()
.name.map((name: string) => name.split(' ').reverse().join(', '))
.get()

assert.deepEqual(surnameFirstPeople, [
{ name: 'Wallace, John' },
{ name: 'Collins, Michael' },
{ name: 'Sanchez, Mary' },
])

// Using abstract lenses

const allCompanies = [
Expand Down
Loading

0 comments on commit a2ff948

Please sign in to comment.