├── dtslint ├── ts3.5 │ ├── index.d.ts │ ├── tslint.json │ ├── tsconfig.json │ ├── Iso.ts │ ├── Traversal.ts │ ├── Lens.ts │ ├── Optional.ts │ ├── Prism.ts │ └── index.ts └── index.d.ts ├── .gitignore ├── .vscode └── settings.json ├── .prettierrc ├── docs ├── modules │ ├── index.md │ ├── At │ │ ├── Set.ts.md │ │ ├── Record.ts.md │ │ ├── ReadonlySet.ts.md │ │ └── ReadonlyRecord.ts.md │ ├── internal.ts.md │ ├── Index │ │ ├── Array.ts.md │ │ ├── Record.ts.md │ │ ├── NonEmptyArray.ts.md │ │ ├── ReadonlyArray.ts.md │ │ ├── ReadonlyRecord.ts.md │ │ └── ReadonlyNonEmptyArray.ts.md │ ├── Either.ts.md │ ├── At.ts.md │ ├── Ix.ts.md │ ├── Lens.ts.md │ ├── Iso.ts.md │ └── Prism.ts.md ├── _config.yml └── index.md ├── tsconfig.build.json ├── tsconfig.build-es6.json ├── test ├── util.ts ├── 2.2 │ ├── Getter.ts │ ├── Either.ts │ ├── Setter.ts │ ├── Iso.ts │ ├── Fold.ts │ ├── conversions.ts │ ├── Traversal.ts │ ├── At.ts │ ├── Prism.ts │ ├── Lens.ts │ ├── Optional.ts │ └── Index.ts ├── Ix.ts ├── At.ts ├── Traversal.ts ├── Lens.ts ├── Optional.ts ├── Iso.ts └── Prism.ts ├── .github ├── ISSUE_TEMPLATE │ ├── Documentation.md │ ├── Bug_report.md │ └── Feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── scripts ├── pre-publish.ts ├── run.ts ├── release.ts ├── release-as-next.ts ├── FileSystem.ts └── build.ts ├── src ├── Index │ ├── ReadonlyArray.ts │ ├── Record.ts │ ├── ReadonlyRecord.ts │ ├── ReadonlyNonEmptyArray.ts │ ├── Array.ts │ └── NonEmptyArray.ts ├── At │ ├── ReadonlySet.ts │ ├── ReadonlyRecord.ts │ ├── Set.ts │ └── Record.ts ├── Either.ts ├── At.ts └── Ix.ts ├── examples ├── tsconfig.json ├── Iso.ts ├── Traversal.ts ├── Fold.ts ├── Optional.ts ├── Prism.ts ├── introduction.ts └── Lens.ts ├── tslint.json ├── jest.config.js ├── tsconfig.json ├── LICENSE ├── package.json ├── perf └── 119.ts ├── README.md └── CHANGELOG.md /dtslint/ts3.5/index.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dtslint/index.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript Version: 3.5 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | lib 4 | es6 5 | dev 6 | coverage 7 | dist 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120, 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /docs/modules/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Modules 3 | has_children: true 4 | permalink: /docs/modules 5 | nav_order: 2 6 | --- 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false 5 | }, 6 | "include": ["./src"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.build-es6.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/es6", 5 | "module": "es6" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | 3 | export const deepStrictEqual = (actual: A, expected: A) => { 4 | assert.deepStrictEqual(actual, expected) 5 | } 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Documentation" 3 | about: Imrovements or suggestions of monocle-ts documentation 4 | --- 5 | 6 | ## 📖 Documentation 7 | -------------------------------------------------------------------------------- /dtslint/ts3.5/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dtslint.json", 3 | "rules": { 4 | "semicolon": false, 5 | "array-type": false, 6 | "no-unnecessary-generics": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /scripts/pre-publish.ts: -------------------------------------------------------------------------------- 1 | import { left } from 'fp-ts/lib/TaskEither' 2 | import { run } from './run' 3 | 4 | const main = left(new Error('"npm publish" can not be run from root, run "npm run release" instead')) 5 | 6 | run(main) 7 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: pmarsceill/just-the-docs 2 | 3 | # Enable or disable the site search 4 | search_enabled: true 5 | 6 | # Aux links for the upper right navigation 7 | aux_links: 8 | 'monocle-ts on GitHub': 9 | - 'https://github.com/gcanti/monocle-ts' 10 | -------------------------------------------------------------------------------- /src/Index/ReadonlyArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.2.0 3 | */ 4 | import { Index } from '..' 5 | import * as A from './Array' 6 | 7 | /** 8 | * @category constructor 9 | * @since 2.2.0 10 | */ 11 | export const indexReadonlyArray: () => Index, number, A> = A.indexArray as any 12 | -------------------------------------------------------------------------------- /src/At/ReadonlySet.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.2.0 3 | */ 4 | import { Eq } from 'fp-ts/lib/Eq' 5 | import { At } from '..' 6 | import * as S from './Set' 7 | 8 | /** 9 | * @category constructor 10 | * @since 2.2.0 11 | */ 12 | export const atReadonlySet: (E: Eq) => At, A, boolean> = S.atSet as any 13 | -------------------------------------------------------------------------------- /src/Index/Record.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 1.7.0 3 | */ 4 | import { Index } from '..' 5 | import { atRecord } from '../At/Record' 6 | 7 | /** 8 | * @category constructor 9 | * @since 1.7.0 10 | */ 11 | export function indexRecord(): Index, string, A> { 12 | return Index.fromAt(atRecord()) 13 | } 14 | -------------------------------------------------------------------------------- /src/Index/ReadonlyRecord.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.2.0 3 | */ 4 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 5 | import { Index } from '..' 6 | import * as R from './Record' 7 | 8 | /** 9 | * @category constructor 10 | * @since 2.2.0 11 | */ 12 | export const indexReadonlyRecord: () => Index, string, A> = R.indexRecord 13 | -------------------------------------------------------------------------------- /src/At/ReadonlyRecord.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.2.0 3 | */ 4 | import { Option } from 'fp-ts/lib/Option' 5 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 6 | import { At } from '..' 7 | import * as R from './Record' 8 | 9 | /** 10 | * @category constructor 11 | * @since 2.2.0 12 | */ 13 | export const atReadonlyRecord: () => At, string, Option> = R.atRecord 14 | -------------------------------------------------------------------------------- /test/2.2/Getter.ts: -------------------------------------------------------------------------------- 1 | import { Getter } from '../../src' 2 | import * as assert from 'assert' 3 | 4 | type Point = { 5 | readonly x: number 6 | readonly y: number 7 | } 8 | 9 | const _x = new Getter((p: Point): number => p.x) 10 | 11 | describe('Getter', () => { 12 | const eg0 = { x: 42, y: -1 } 13 | 14 | it('get', () => { 15 | assert.strictEqual(_x.get(eg0), 42) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /dtslint/ts3.5/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "strict": true, 5 | "noImplicitAny": true, 6 | "noImplicitThis": true, 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "noImplicitReturns": false, 10 | "noUnusedLocals": false, 11 | "noUnusedParameters": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "target": "es5", 14 | "lib": ["es2015"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "noImplicitReturns": true, 6 | "noImplicitThis": true, 7 | "noUnusedLocals": false, 8 | "noUnusedParameters": false, 9 | "noFallthroughCasesInSwitch": true, 10 | "noEmitOnError": true, 11 | "strictNullChecks": true, 12 | "target": "es5", 13 | "forceConsistentCasingInFileNames": true, 14 | "lib": [ 15 | "es6" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/modules/At/Set.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: At/Set.ts 3 | nav_order: 5 4 | parent: Modules 5 | --- 6 | 7 | ## Set overview 8 | 9 | Added in v1.2.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [atSet](#atset) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## atSet 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare function atSet
(E: Eq): At, A, boolean> 28 | ``` 29 | 30 | Added in v1.2.0 31 | -------------------------------------------------------------------------------- /docs/modules/internal.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: internal.ts 3 | nav_order: 14 4 | parent: Modules 5 | --- 6 | 7 | ## internal overview 8 | 9 | **This module is experimental** 10 | 11 | Experimental features are published in order to get early feedback from the community. 12 | 13 | A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 14 | 15 | Added in v2.3.0 16 | 17 | --- 18 | 19 |

Table of contents

20 | 21 | --- 22 | -------------------------------------------------------------------------------- /examples/Iso.ts: -------------------------------------------------------------------------------- 1 | import { Iso } from '../src' 2 | 3 | const mTokm = new Iso( 4 | (m) => m / 1000, 5 | (km) => km * 1000 6 | ) 7 | 8 | console.log(mTokm.get(100)) // => 0.1 9 | console.log(mTokm.reverseGet(1.2)) // => 1200 10 | 11 | const kmToMile = new Iso( 12 | (km) => km / 1.60934, 13 | (miles) => miles * 1.60934 14 | ) 15 | 16 | // composition 17 | const mToMile = mTokm.compose(kmToMile) 18 | 19 | console.log(mToMile.get(100)) // => 0.06213727366498068 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Before submitting a pull request,** please make sure the following is done: 2 | 3 | - Fork [the repository](https://github.com/gcanti/monocle-ts) and create your branch from `master`. 4 | - Run `npm install` in the repository root. 5 | - If you've fixed a bug or added code that should be tested, add tests! 6 | - Ensure the test suite passes (`npm test`). 7 | 8 | **Note**. If you find a typo in the **documentation**, make sure to modify the corresponding source (docs are generated). 9 | -------------------------------------------------------------------------------- /scripts/run.ts: -------------------------------------------------------------------------------- 1 | import { fold } from 'fp-ts/lib/Either' 2 | import { TaskEither } from 'fp-ts/lib/TaskEither' 3 | 4 | export function run
(eff: TaskEither): void { 5 | eff() 6 | .then( 7 | fold( 8 | (e) => { 9 | throw e 10 | }, 11 | (_) => { 12 | process.exitCode = 0 13 | } 14 | ) 15 | ) 16 | .catch((e) => { 17 | console.error(e) // tslint:disable-line no-console 18 | 19 | process.exitCode = 1 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-standard", "tslint-immutable"], 3 | "rules": { 4 | "space-before-function-paren": false, 5 | "no-use-before-declare": false, 6 | "variable-name": false, 7 | "quotemark": [true, "single", "jsx-double"], 8 | "ter-indent": false, 9 | "strict-boolean-expressions": true, 10 | "forin": true, 11 | "no-console": true, 12 | "array-type": [true, "generic"], 13 | "readonly-keyword": true, 14 | "readonly-array": false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/modules/Index/Array.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Index/Array.ts 3 | nav_order: 8 4 | parent: Modules 5 | --- 6 | 7 | ## Array overview 8 | 9 | Added in v1.2.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [indexArray](#indexarray) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## indexArray 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare function indexArray
(): Index, number, A> 28 | ``` 29 | 30 | Added in v1.2.0 31 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | collectCoverage: true, 5 | collectCoverageFrom: ['src/**/*.ts', '!src/index.ts'], 6 | transform: { 7 | '^.+\\.tsx?$': 'ts-jest' 8 | }, 9 | testRegex: 'test', 10 | moduleFileExtensions: ['ts', 'js'], 11 | coverageThreshold: { 12 | global: { 13 | branches: 100, 14 | functions: 100, 15 | lines: 100, 16 | statements: 100 17 | } 18 | }, 19 | modulePathIgnorePatterns: ['util'] 20 | } 21 | -------------------------------------------------------------------------------- /docs/modules/At/Record.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: At/Record.ts 3 | nav_order: 4 4 | parent: Modules 5 | --- 6 | 7 | ## Record overview 8 | 9 | Added in v1.7.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [atRecord](#atrecord) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## atRecord 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare function atRecord
(): At, string, Option> 28 | ``` 29 | 30 | Added in v1.7.0 31 | -------------------------------------------------------------------------------- /docs/modules/Index/Record.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Index/Record.ts 3 | nav_order: 13 4 | parent: Modules 5 | --- 6 | 7 | ## Record overview 8 | 9 | Added in v1.7.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [indexRecord](#indexrecord) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## indexRecord 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare function indexRecord
(): Index, string, A> 28 | ``` 29 | 30 | Added in v1.7.0 31 | -------------------------------------------------------------------------------- /src/Index/ReadonlyNonEmptyArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.2.0 3 | */ 4 | import { Index } from '..' 5 | import * as NEA from './NonEmptyArray' 6 | 7 | /** 8 | * @category model 9 | * @since 2.2.0 10 | */ 11 | export interface ReadonlyNonEmptyArray extends ReadonlyArray { 12 | readonly 0: A 13 | } 14 | 15 | /** 16 | * @category constructor 17 | * @since 2.2.0 18 | */ 19 | export const indexReadonlyNonEmptyArray: () => Index< 20 | ReadonlyNonEmptyArray, 21 | number, 22 | A 23 | > = NEA.indexNonEmptyArray as any 24 | -------------------------------------------------------------------------------- /dtslint/ts3.5/Iso.ts: -------------------------------------------------------------------------------- 1 | import * as _ from '../../src/Iso' 2 | import { pipe } from 'fp-ts/lib/pipeable' 3 | 4 | interface A { 5 | a: string 6 | b: number 7 | c: string | boolean 8 | } 9 | 10 | declare const isoC: _.Iso 11 | 12 | // 13 | // modify 14 | // 15 | 16 | // $ExpectType (s: A) => A 17 | pipe(isoC, _.modify(( 18 | a // $ExpectType string | boolean 19 | ) => a)) 20 | 21 | // $ExpectType (s: A) => A 22 | pipe(isoC, _.modify(() => 'foo')) 23 | 24 | // $ExpectType (s: A) => A 25 | pipe(isoC, _.modify(() => 'foo')) 26 | -------------------------------------------------------------------------------- /examples/Traversal.ts: -------------------------------------------------------------------------------- 1 | import { fromTraversable } from '../src' 2 | import { array } from 'fp-ts/lib/Array' 3 | 4 | const eachL = fromTraversable(array)() 5 | 6 | const xs = [1, 2, 3, 4] 7 | 8 | console.log(eachL.set(0)(xs)) // => [ 0, 0, 0, 0 ] 9 | 10 | console.log(eachL.modify((n: number) => n + 1)(xs)) // => [ 2, 3, 4, 5 ] 11 | 12 | const fold = eachL.asFold() 13 | 14 | console.log(fold.getAll(xs)) 15 | console.log(fold.headOption(xs)) 16 | console.log(fold.find((n) => n > 2)(xs)) 17 | console.log(fold.all((n: number) => n % 2 === 0)(xs)) 18 | -------------------------------------------------------------------------------- /docs/modules/At/ReadonlySet.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: At/ReadonlySet.ts 3 | nav_order: 3 4 | parent: Modules 5 | --- 6 | 7 | ## ReadonlySet overview 8 | 9 | Added in v2.2.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [atReadonlySet](#atreadonlyset) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## atReadonlySet 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare const atReadonlySet:
(E: Eq) => At, A, boolean> 28 | ``` 29 | 30 | Added in v2.2.0 31 | -------------------------------------------------------------------------------- /examples/Fold.ts: -------------------------------------------------------------------------------- 1 | import { Fold, fromFoldable } from '../src' 2 | import { array } from 'fp-ts/lib/Array' 3 | import { monoidProduct, monoidSum } from 'fp-ts/lib/Monoid' 4 | import { identity } from 'fp-ts/lib/function' 5 | 6 | const xs = ['a', 'bb'] 7 | const fold = fromFoldable(array)() 8 | const len = (s: string) => s.length 9 | 10 | console.log(fold.foldMap(monoidSum)(len)(xs)) 11 | console.log(fold.foldMap(monoidProduct)(len)(xs)) 12 | 13 | import * as either from 'fp-ts/lib/Either' 14 | 15 | const fold2 = fromFoldable(either)() 16 | -------------------------------------------------------------------------------- /docs/modules/Index/NonEmptyArray.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Index/NonEmptyArray.ts 3 | nav_order: 9 4 | parent: Modules 5 | --- 6 | 7 | ## NonEmptyArray overview 8 | 9 | Added in v1.5.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [indexNonEmptyArray](#indexnonemptyarray) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## indexNonEmptyArray 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare function indexNonEmptyArray
(): Index, number, A> 28 | ``` 29 | 30 | Added in v1.5.0 31 | -------------------------------------------------------------------------------- /docs/modules/Index/ReadonlyArray.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Index/ReadonlyArray.ts 3 | nav_order: 10 4 | parent: Modules 5 | --- 6 | 7 | ## ReadonlyArray overview 8 | 9 | Added in v2.2.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [indexReadonlyArray](#indexreadonlyarray) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## indexReadonlyArray 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare const indexReadonlyArray:
() => Index 28 | ``` 29 | 30 | Added in v2.2.0 31 | -------------------------------------------------------------------------------- /docs/modules/At/ReadonlyRecord.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: At/ReadonlyRecord.ts 3 | nav_order: 2 4 | parent: Modules 5 | --- 6 | 7 | ## ReadonlyRecord overview 8 | 9 | Added in v2.2.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [atReadonlyRecord](#atreadonlyrecord) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## atReadonlyRecord 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare const atReadonlyRecord:
() => At>, string, Option> 28 | ``` 29 | 30 | Added in v2.2.0 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "outDir": "./dist/lib", 5 | "target": "es5", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "lib": ["es6", "dom"], 9 | "sourceMap": false, 10 | "declaration": true, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "stripInternal": true 18 | }, 19 | "include": ["./src", "./test", "./scripts"] 20 | } 21 | -------------------------------------------------------------------------------- /docs/modules/Index/ReadonlyRecord.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Index/ReadonlyRecord.ts 3 | nav_order: 12 4 | parent: Modules 5 | --- 6 | 7 | ## ReadonlyRecord overview 8 | 9 | Added in v2.2.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [indexReadonlyRecord](#indexreadonlyrecord) 17 | 18 | --- 19 | 20 | # constructor 21 | 22 | ## indexReadonlyRecord 23 | 24 | **Signature** 25 | 26 | ```ts 27 | export declare const indexReadonlyRecord:
() => Index>, string, A> 28 | ``` 29 | 30 | Added in v2.2.0 31 | -------------------------------------------------------------------------------- /src/At/Set.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 1.2.0 3 | */ 4 | import { At, Lens } from '..' 5 | import { Eq } from 'fp-ts/lib/Eq' 6 | import * as S from 'fp-ts/lib/Set' 7 | 8 | /** 9 | * @category constructor 10 | * @since 1.2.0 11 | */ 12 | export function atSet(E: Eq): At, A, boolean> { 13 | const elemE = S.elem(E) 14 | const insertE = S.insert(E) 15 | const removeE = S.remove(E) 16 | return new At((at) => { 17 | const insertEAt = insertE(at) 18 | const removeEAt = removeE(at) 19 | return new Lens( 20 | (s) => elemE(at, s), 21 | (a) => (s) => (a ? insertEAt(s) : removeEAt(s)) 22 | ) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/Either.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 1.6.0 3 | */ 4 | import { Either, right, left, fold } from 'fp-ts/lib/Either' 5 | import { fromEither, none, some } from 'fp-ts/lib/Option' 6 | import { Prism } from '.' 7 | 8 | const r = new Prism, any>(fromEither, right) 9 | 10 | /** 11 | * @category constructor 12 | * @since 1.6.0 13 | */ 14 | export const _right = (): Prism, A> => r 15 | 16 | const l = new Prism, any>( 17 | fold(some, () => none), 18 | left 19 | ) 20 | 21 | /** 22 | * @category constructor 23 | * @since 1.6.0 24 | */ 25 | export const _left = (): Prism, E> => l 26 | -------------------------------------------------------------------------------- /scripts/release.ts: -------------------------------------------------------------------------------- 1 | import { run } from './run' 2 | import * as child_process from 'child_process' 3 | import { left, right } from 'fp-ts/lib/Either' 4 | import * as TE from 'fp-ts/lib/TaskEither' 5 | 6 | const DIST = 'dist' 7 | 8 | const exec = (cmd: string, args?: child_process.ExecOptions): TE.TaskEither => () => 9 | new Promise((resolve) => { 10 | child_process.exec(cmd, args, (err) => { 11 | if (err !== null) { 12 | return resolve(left(err)) 13 | } 14 | 15 | return resolve(right(undefined)) 16 | }) 17 | }) 18 | 19 | export const main = exec('npm publish', { 20 | cwd: DIST 21 | }) 22 | 23 | run(main) 24 | -------------------------------------------------------------------------------- /test/2.2/Either.ts: -------------------------------------------------------------------------------- 1 | import { _right, _left } from '../../src/Either' 2 | import { right, left } from 'fp-ts/lib/Either' 3 | import { some, none } from 'fp-ts/lib/Option' 4 | import * as U from '../util' 5 | 6 | describe('Either', () => { 7 | it('_right', () => { 8 | const prism = _right() 9 | U.deepStrictEqual(prism.getOption(right(1)), some(1)) 10 | U.deepStrictEqual(prism.getOption(left('a')), none) 11 | }) 12 | 13 | it('_left', () => { 14 | const prism = _left() 15 | U.deepStrictEqual(prism.getOption(right(1)), none) 16 | U.deepStrictEqual(prism.getOption(left('a')), some('a')) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/2.2/Setter.ts: -------------------------------------------------------------------------------- 1 | import { Setter } from '../../src' 2 | import * as assert from 'assert' 3 | 4 | type Point = { 5 | readonly x: number 6 | readonly y: number 7 | } 8 | 9 | const _x = new Setter((f) => (s) => ({ 10 | x: f(s.x), 11 | y: s.y 12 | })) 13 | 14 | describe('Setter', () => { 15 | const double = (n: number) => n * 2 16 | const eg0 = { x: 42, y: -1 } 17 | 18 | it('modify', () => { 19 | const eg1 = _x.modify(double)(eg0) 20 | assert.strictEqual(eg1.x, double(eg0.x)) 21 | assert.strictEqual(eg1.y, eg0.y) 22 | }) 23 | 24 | it('set', () => { 25 | assert.strictEqual(_x.set(0)(eg0).x, 0) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /src/At/Record.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 1.7.0 3 | */ 4 | import { At, Lens } from '..' 5 | import { Option, isNone } from 'fp-ts/lib/Option' 6 | import * as R from 'fp-ts/lib/Record' 7 | 8 | /** 9 | * @category constructor 10 | * @since 1.7.0 11 | */ 12 | export function atRecord(): At, string, Option> { 13 | return new At( 14 | (k) => 15 | new Lens( 16 | (r) => R.lookup(k, r), 17 | (oa) => (r) => { 18 | if (isNone(oa)) { 19 | return R.deleteAt(k)(r) 20 | } else { 21 | return R.insertAt(k, oa.value)(r) 22 | } 23 | } 24 | ) 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /scripts/release-as-next.ts: -------------------------------------------------------------------------------- 1 | import { run } from './run' 2 | import * as child_process from 'child_process' 3 | import { left, right } from 'fp-ts/lib/Either' 4 | import * as TE from 'fp-ts/lib/TaskEither' 5 | 6 | const DIST = 'dist' 7 | 8 | const exec = (cmd: string, args?: child_process.ExecOptions): TE.TaskEither => () => 9 | new Promise((resolve) => { 10 | child_process.exec(cmd, args, (err) => { 11 | if (err !== null) { 12 | return resolve(left(err)) 13 | } 14 | 15 | return resolve(right(undefined)) 16 | }) 17 | }) 18 | 19 | export const main = exec('npm publish --tag=next', { 20 | cwd: DIST 21 | }) 22 | 23 | run(main) 24 | -------------------------------------------------------------------------------- /docs/modules/Either.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Either.ts 3 | nav_order: 6 4 | parent: Modules 5 | --- 6 | 7 | ## Either overview 8 | 9 | Added in v1.6.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [\_left](#_left) 17 | - [\_right](#_right) 18 | 19 | --- 20 | 21 | # constructor 22 | 23 | ## \_left 24 | 25 | **Signature** 26 | 27 | ```ts 28 | export declare const _left: () => Prism, E> 29 | ``` 30 | 31 | Added in v1.6.0 32 | 33 | ## \_right 34 | 35 | **Signature** 36 | 37 | ```ts 38 | export declare const _right: () => Prism, A> 39 | ``` 40 | 41 | Added in v1.6.0 42 | -------------------------------------------------------------------------------- /src/Index/Array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 1.2.0 3 | */ 4 | import { Index, Optional } from '..' 5 | import { lookup, updateAt } from 'fp-ts/lib/Array' 6 | import { isNone } from 'fp-ts/lib/Option' 7 | 8 | /** 9 | * @category constructor 10 | * @since 1.2.0 11 | */ 12 | export function indexArray
(): Index, number, A> { 13 | return new Index( 14 | (i) => 15 | new Optional( 16 | (as) => lookup(i, as), 17 | (a) => (as) => { 18 | const oas = updateAt(i, a)(as) 19 | if (isNone(oas)) { 20 | return as 21 | } else { 22 | return oas.value 23 | } 24 | } 25 | ) 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | # Controls when the action will run. 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | matrix: 20 | node-version: [12.x] 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm install 29 | - run: npm run build --if-present 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /src/Index/NonEmptyArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 1.5.0 3 | */ 4 | import { Index, Optional } from '..' 5 | import { NonEmptyArray, updateAt } from 'fp-ts/lib/NonEmptyArray' 6 | import { lookup } from 'fp-ts/lib/Array' 7 | import { isNone } from 'fp-ts/lib/Option' 8 | 9 | /** 10 | * @category constructor 11 | * @since 1.5.0 12 | */ 13 | export function indexNonEmptyArray(): Index, number, A> { 14 | return new Index( 15 | (i) => 16 | new Optional( 17 | (s) => lookup(i, s), 18 | (a) => (nea) => { 19 | const onea = updateAt(i, a)(nea) 20 | if (isNone(onea)) { 21 | return nea 22 | } else { 23 | return onea.value 24 | } 25 | } 26 | ) 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /dtslint/ts3.5/Traversal.ts: -------------------------------------------------------------------------------- 1 | import * as T from '../../src/Traversal' 2 | import { pipe } from 'fp-ts/lib/pipeable' 3 | 4 | interface A { 5 | a: string 6 | b: number 7 | c: string | boolean 8 | } 9 | 10 | declare const traversalC: T.Traversal 11 | 12 | // 13 | // modify 14 | // 15 | 16 | // $ExpectType (s: A) => A 17 | pipe(traversalC, T.modify(( 18 | a // $ExpectType string | boolean 19 | ) => a)) 20 | 21 | // $ExpectType (s: A) => A 22 | pipe(traversalC, T.modify(() => 'foo')) 23 | 24 | // $ExpectType (s: A) => A 25 | pipe(traversalC, T.modify(() => 'foo')) 26 | 27 | // $ExpectError 28 | pipe(T.id(), T.props()) 29 | // $ExpectError 30 | pipe(T.id(), T.props('a')) 31 | 32 | pipe(T.id(), T.props('a', 'b')) // $ExpectType Traversal 33 | -------------------------------------------------------------------------------- /docs/modules/Index/ReadonlyNonEmptyArray.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Index/ReadonlyNonEmptyArray.ts 3 | nav_order: 11 4 | parent: Modules 5 | --- 6 | 7 | ## ReadonlyNonEmptyArray overview 8 | 9 | Added in v2.2.0 10 | 11 | --- 12 | 13 |

Table of contents

14 | 15 | - [constructor](#constructor) 16 | - [indexReadonlyNonEmptyArray](#indexreadonlynonemptyarray) 17 | - [model](#model) 18 | - [ReadonlyNonEmptyArray (interface)](#readonlynonemptyarray-interface) 19 | 20 | --- 21 | 22 | # constructor 23 | 24 | ## indexReadonlyNonEmptyArray 25 | 26 | **Signature** 27 | 28 | ```ts 29 | export declare const indexReadonlyNonEmptyArray:
() => Index, number, A> 30 | ``` 31 | 32 | Added in v2.2.0 33 | 34 | # model 35 | 36 | ## ReadonlyNonEmptyArray (interface) 37 | 38 | **Signature** 39 | 40 | ```ts 41 | export interface ReadonlyNonEmptyArray extends ReadonlyArray { 42 | readonly 0: A 43 | } 44 | ``` 45 | 46 | Added in v2.2.0 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help make monocle-ts better 4 | --- 5 | 6 | ## 🐛 Bug report 7 | 8 | ### Current Behavior 9 | 10 | 11 | 12 | ### Expected behavior 13 | 14 | 15 | 16 | ### Reproducible example 17 | 18 | ### Suggested solution(s) 19 | 20 | 21 | 22 | ### Additional context 23 | 24 | 25 | 26 | ### Your environment 27 | 28 | Which versions of monocle-ts are affected by this issue? Did this work in previous versions of monocle-ts? 29 | 30 | 31 | 32 | | Software | Version(s) | 33 | | ---------- | ---------- | 34 | | monocle-ts | | 35 | | fp-ts | | 36 | | TypeScript | | 37 | -------------------------------------------------------------------------------- /dtslint/ts3.5/Lens.ts: -------------------------------------------------------------------------------- 1 | import * as L from '../../src/Lens' 2 | import { pipe } from 'fp-ts/lib/pipeable' 3 | 4 | interface A { 5 | a: string 6 | b: number 7 | c: string | boolean 8 | } 9 | 10 | declare const lensC: L.Lens 11 | 12 | // 13 | // modify 14 | // 15 | 16 | // $ExpectType (s: A) => A 17 | pipe(lensC, L.modify(( 18 | a // $ExpectType string | boolean 19 | ) => a)) 20 | 21 | // $ExpectType (s: A) => A 22 | pipe(lensC, L.modify(() => 'foo')) 23 | 24 | // $ExpectType (s: A) => A 25 | pipe(lensC, L.modify(() => 'foo')) 26 | 27 | // 28 | // prop 29 | // 30 | 31 | // $ExpectError 32 | pipe(L.id(), L.prop('d')) 33 | 34 | // 35 | // props 36 | // 37 | 38 | // $ExpectError 39 | pipe(L.id(), L.props()) 40 | // $ExpectError 41 | pipe(L.id(), L.props('a')) 42 | 43 | pipe(L.id(), L.props('a', 'b')) // $ExpectType Lens 44 | 45 | // 46 | // component 47 | // 48 | 49 | // $ExpectError 50 | pipe(L.id<{ 1: number }>(), L.component(1)) 51 | -------------------------------------------------------------------------------- /examples/Optional.ts: -------------------------------------------------------------------------------- 1 | import { Lens, Optional, Prism } from '../src' 2 | import { Option, some, none } from 'fp-ts/lib/Option' 3 | import { nameLens, employee } from './Lens' 4 | 5 | const head = new Optional( 6 | (s) => (s.length > 0 ? some(s[0]) : none), 7 | (a) => (s) => (s.length > 0 ? a + s.substring(1) : '') 8 | ) 9 | 10 | console.log(head.getOption('')) // => None 11 | console.log(head.getOption('hello')) // => Some('h') 12 | console.log(head.set('H')('')) // => '' 13 | console.log(head.set('H')('hello')) // => 'Hello' 14 | 15 | const optional = nameLens.asOptional().compose(head) 16 | 17 | console.log(JSON.stringify(optional.modify((s) => s.toUpperCase())(employee), null, 2)) 18 | 19 | interface Person { 20 | name: string 21 | surname: Option 22 | } 23 | 24 | const surname = Lens.fromProp()('surname').composePrism(Prism.some()) 25 | 26 | const p: Person = { name: 'Giulio', surname: none } 27 | 28 | console.log(surname.getOption(p)) // => none 29 | console.log(surname.set('Canti')(p)) // => { name: 'Giulio', surname: none } 30 | -------------------------------------------------------------------------------- /test/Ix.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as _ from '../src/Ix' 3 | import * as O from 'fp-ts/lib/Option' 4 | import { eqString } from 'fp-ts/lib/Eq' 5 | import * as U from './util' 6 | 7 | describe('Ix', () => { 8 | it('indexReadonlyMap', () => { 9 | const index = _.indexReadonlyMap(eqString)().index('a') 10 | U.deepStrictEqual(index.getOption(new Map([])), O.none) 11 | U.deepStrictEqual( 12 | index.getOption( 13 | new Map([ 14 | ['a', 1], 15 | ['b', 2] 16 | ]) 17 | ), 18 | O.some(1) 19 | ) 20 | U.deepStrictEqual(index.set(3)(new Map([['b', 2]])), new Map([['b', 2]])) 21 | U.deepStrictEqual( 22 | index.set(3)( 23 | new Map([ 24 | ['a', 1], 25 | ['b', 2] 26 | ]) 27 | ), 28 | new Map([ 29 | ['a', 3], 30 | ['b', 2] 31 | ]) 32 | ) 33 | // should return the same reference if nothing changed 34 | const x = new Map([ 35 | ['a', 1], 36 | ['b', 2] 37 | ]) 38 | assert.strictEqual(index.set(1)(x), x) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Giulio Canti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680Feature request" 3 | about: Suggest an idea for monocle-ts 4 | --- 5 | 6 | ## 🚀 Feature request 7 | 8 | ### Current Behavior 9 | 10 | 11 | 12 | ### Desired Behavior 13 | 14 | 15 | 16 | ### Suggested Solution 17 | 18 | 19 | 20 | 21 | 22 | ### Who does this impact? Who is this for? 23 | 24 | 25 | 26 | ### Describe alternatives you've considered 27 | 28 | 29 | 30 | ### Additional context 31 | 32 | 33 | -------------------------------------------------------------------------------- /dtslint/ts3.5/Optional.ts: -------------------------------------------------------------------------------- 1 | import * as O from '../../src/Optional' 2 | import { pipe } from 'fp-ts/lib/pipeable' 3 | 4 | interface A { 5 | a: string 6 | b: number 7 | c: string | boolean 8 | } 9 | 10 | declare const optionalC: O.Optional 11 | 12 | // 13 | // modifyOption 14 | // 15 | 16 | // $ExpectType (s: A) => Option 17 | pipe(optionalC, O.modifyOption(( 18 | a // $ExpectType string | boolean 19 | ) => a)) 20 | 21 | // $ExpectType (s: A) => Option 22 | pipe(optionalC, O.modifyOption(() => 'foo')) 23 | 24 | // $ExpectType (s: A) => Option 25 | pipe(optionalC, O.modifyOption(() => 'foo')) 26 | 27 | // 28 | // modify 29 | // 30 | 31 | // $ExpectType (s: A) => A 32 | pipe(optionalC, O.modify(( 33 | a // $ExpectType string | boolean 34 | ) => a)) 35 | 36 | // $ExpectType (s: A) => A 37 | pipe(optionalC, O.modify(() => 'foo')) 38 | 39 | // $ExpectType (s: A) => A 40 | pipe(optionalC, O.modify(() => 'foo')) 41 | 42 | // $ExpectError 43 | pipe(O.id(), O.props()) 44 | // $ExpectError 45 | pipe(O.id(), O.props('a')) 46 | 47 | pipe(O.id(), O.props('a', 'b')) // $ExpectType Optional 48 | -------------------------------------------------------------------------------- /dtslint/ts3.5/Prism.ts: -------------------------------------------------------------------------------- 1 | import * as P from '../../src/Prism' 2 | import { pipe } from 'fp-ts/lib/pipeable' 3 | 4 | interface A { 5 | a: string 6 | b: number 7 | c: string | boolean 8 | } 9 | 10 | declare const prismC: P.Prism 11 | 12 | // 13 | // modifyOption 14 | // 15 | 16 | // $ExpectType (s: A) => Option 17 | pipe(prismC, P.modifyOption(( 18 | a // $ExpectType string | boolean 19 | ) => a)) 20 | 21 | // $ExpectType (s: A) => Option 22 | pipe(prismC, P.modifyOption(() => 'foo')) 23 | 24 | // $ExpectType (s: A) => Option 25 | pipe(prismC, P.modifyOption(() => 'foo')) 26 | 27 | // 28 | // modify 29 | // 30 | 31 | // $ExpectType (s: A) => A 32 | pipe(prismC, P.modify(( 33 | a // $ExpectType string | boolean 34 | ) => a)) 35 | 36 | // $ExpectType (s: A) => A 37 | pipe(prismC, P.modify(() => 'foo')) 38 | 39 | // $ExpectType (s: A) => A 40 | pipe(prismC, P.modify(() => 'foo')) 41 | 42 | // 43 | // props 44 | // 45 | 46 | // $ExpectError 47 | pipe(P.id(), P.props()) 48 | // $ExpectError 49 | pipe(P.id(), P.props('a')) 50 | 51 | pipe(P.id(), P.props('a', 'b')) // $ExpectType Optional 52 | -------------------------------------------------------------------------------- /scripts/FileSystem.ts: -------------------------------------------------------------------------------- 1 | import * as TE from 'fp-ts/lib/TaskEither' 2 | import { flow } from 'fp-ts/lib/function' 3 | import * as fs from 'fs' 4 | import * as G from 'glob' 5 | 6 | export interface FileSystem { 7 | readonly readFile: (path: string) => TE.TaskEither 8 | readonly writeFile: (path: string, content: string) => TE.TaskEither 9 | readonly copyFile: (from: string, to: string) => TE.TaskEither 10 | readonly glob: (pattern: string) => TE.TaskEither> 11 | readonly mkdir: (path: string) => TE.TaskEither 12 | } 13 | 14 | const readFile = TE.taskify(fs.readFile) 15 | const writeFile = TE.taskify(fs.writeFile) 16 | const copyFile = TE.taskify(fs.copyFile) 17 | const glob = TE.taskify>(G) 18 | const mkdirTE = TE.taskify(fs.mkdir) 19 | 20 | export const fileSystem: FileSystem = { 21 | readFile: (path) => readFile(path, 'utf8'), 22 | writeFile, 23 | copyFile, 24 | glob, 25 | mkdir: flow( 26 | mkdirTE, 27 | TE.map(() => undefined), 28 | TE.orElse((e) => (e.code === 'EEXIST' ? TE.right(undefined) : TE.left(e))) 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /examples/Prism.ts: -------------------------------------------------------------------------------- 1 | import { Prism } from '../src' 2 | import { Option, some, none } from 'fp-ts/lib/Option' 3 | 4 | class JStr { 5 | constructor(public readonly value: string) {} 6 | } 7 | class JNum { 8 | constructor(public readonly value: number) {} 9 | } 10 | class JObj { 11 | constructor(public readonly value: { [key: string]: Json }) {} 12 | } 13 | 14 | type Json = null | JStr | JNum | JObj 15 | 16 | const jStr = new Prism( 17 | (s) => (s instanceof JStr ? some(s.value) : none), 18 | (a) => new JStr(a) 19 | ) 20 | 21 | console.log(jStr.getOption(new JStr('hello'))) 22 | console.log(jStr.getOption(new JNum(1))) 23 | 24 | // a function is applied only if there is a match 25 | const reverse = (s: string): string => s.split('').reverse().join('') 26 | console.log(jStr.modify(reverse)(new JStr('hello'))) 27 | console.log(jStr.modify(reverse)(new JNum(1))) 28 | console.log(jStr.modifyOption(reverse)(new JStr('hello'))) 29 | console.log(jStr.modifyOption(reverse)(new JNum(1))) 30 | 31 | // composizione 32 | const jNum = new Prism( 33 | (s) => (s instanceof JNum ? some(s.value) : none), 34 | (a) => new JNum(a) 35 | ) 36 | const numberToInt = new Prism( 37 | (s) => (s % 1 === 0 ? some(s) : none), 38 | (a) => a 39 | ) 40 | 41 | const jInt = jNum.compose(numberToInt) 42 | 43 | console.log(jInt.getOption(new JNum(5.0))) 44 | console.log(jInt.getOption(new JNum(5.2))) 45 | -------------------------------------------------------------------------------- /test/2.2/Iso.ts: -------------------------------------------------------------------------------- 1 | import { Iso } from '../../src' 2 | import * as assert from 'assert' 3 | 4 | const mTokm = new Iso( 5 | (m) => m / 1000, 6 | (km) => km * 1000 7 | ) 8 | const kmToMile = new Iso( 9 | (km) => km * 0.621371, 10 | (mile) => mile / 0.621371 11 | ) 12 | 13 | describe('Iso', () => { 14 | it('get', () => { 15 | assert.strictEqual(mTokm.get(100), 0.1) 16 | assert.strictEqual(mTokm.unwrap(100), 0.1) 17 | assert.strictEqual(mTokm.to(100), 0.1) 18 | }) 19 | 20 | it('reverseGet', () => { 21 | assert.strictEqual(mTokm.reverseGet(1.2), 1200) 22 | assert.strictEqual(mTokm.wrap(1.2), 1200) 23 | assert.strictEqual(mTokm.from(1.2), 1200) 24 | }) 25 | 26 | it('modify', () => { 27 | const double = (x: number) => x * 2 28 | assert.strictEqual(mTokm.modify(double)(1000), 2000) 29 | }) 30 | 31 | it('reverse', () => { 32 | const double = (x: number) => x * 2 33 | assert.strictEqual(mTokm.reverse().modify(double)(2000), 4000) 34 | }) 35 | 36 | it('compose', () => { 37 | const composition1 = mTokm.compose(kmToMile) 38 | const composition2 = mTokm.composeIso(kmToMile) 39 | assert.strictEqual(composition1.get(1500).toFixed(2), '0.93') 40 | assert.strictEqual(composition1.reverseGet(1).toFixed(2), '1609.34') 41 | 42 | assert.strictEqual(composition2.get(1500), composition1.get(1500)) 43 | assert.strictEqual(composition2.reverseGet(1), composition1.reverseGet(1)) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /test/2.2/Fold.ts: -------------------------------------------------------------------------------- 1 | import { Getter, fromFoldable } from '../../src' 2 | import { some, none } from 'fp-ts/lib/Option' 3 | import { array } from 'fp-ts/lib/Array' 4 | import * as U from '../util' 5 | 6 | type Point = { 7 | readonly x: number 8 | readonly y: number 9 | } 10 | 11 | const _x = new Getter((p: Point): number => p.x) 12 | 13 | describe('Fold', () => { 14 | const eg0 = { x: 42, y: -1 } 15 | 16 | it('getAll', () => { 17 | U.deepStrictEqual(_x.asFold().getAll(eg0), [42]) 18 | }) 19 | 20 | it('find', () => { 21 | U.deepStrictEqual(_x.asFold().find((n) => n >= 42)(eg0), some(42)) 22 | U.deepStrictEqual(_x.asFold().find((n) => n < 42)(eg0), none) 23 | }) 24 | 25 | it('exist', () => { 26 | U.deepStrictEqual(_x.asFold().exist((n) => n >= 42)(eg0), true) 27 | U.deepStrictEqual(_x.asFold().exist((n) => n < 42)(eg0), false) 28 | }) 29 | 30 | it('all', () => { 31 | U.deepStrictEqual(_x.asFold().all((n) => n >= 42)(eg0), true) 32 | U.deepStrictEqual(_x.asFold().all((n) => n < 42)(eg0), false) 33 | }) 34 | 35 | it('fromFoldable', () => { 36 | const fold = fromFoldable(array)() 37 | U.deepStrictEqual(fold.all((n) => n >= 2)([1, 2, 3]), false) 38 | U.deepStrictEqual(fold.all((n) => n >= 1)([1, 2, 3]), true) 39 | }) 40 | 41 | it('headOption', () => { 42 | const fold = fromFoldable(array)() 43 | U.deepStrictEqual(fold.headOption([]), none) 44 | U.deepStrictEqual(fold.headOption([1, 2, 3]), some(1)) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/At.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as _ from '../src/At' 3 | import * as O from 'fp-ts/lib/Option' 4 | import { eqString } from 'fp-ts/lib/Eq' 5 | import * as U from './util' 6 | 7 | describe('At', () => { 8 | it('atReadonlyMap', () => { 9 | const at = _.atReadonlyMap(eqString)().at('a') 10 | U.deepStrictEqual(at.get(new Map([])), O.none) 11 | U.deepStrictEqual( 12 | at.get( 13 | new Map([ 14 | ['a', 1], 15 | ['b', 2] 16 | ]) 17 | ), 18 | O.some(1) 19 | ) 20 | U.deepStrictEqual(at.set(O.none)(new Map()), new Map()) 21 | U.deepStrictEqual( 22 | at.set(O.none)( 23 | new Map([ 24 | ['a', 1], 25 | ['b', 2] 26 | ]) 27 | ), 28 | new Map([['b', 2]]) 29 | ) 30 | U.deepStrictEqual( 31 | at.set(O.some(3))( 32 | new Map([ 33 | ['a', 1], 34 | ['b', 2] 35 | ]) 36 | ), 37 | new Map([ 38 | ['a', 3], 39 | ['b', 2] 40 | ]) 41 | ) 42 | // should return the same reference if nothing changed 43 | const x = new Map([ 44 | ['a', 1], 45 | ['b', 2] 46 | ]) 47 | assert.strictEqual(at.set(O.some(1))(x), x) 48 | }) 49 | 50 | it('atReadonlySet', () => { 51 | const at = _.atReadonlySet(eqString).at('a') 52 | U.deepStrictEqual(at.get(new Set()), false) 53 | U.deepStrictEqual(at.get(new Set(['a'])), true) 54 | U.deepStrictEqual(at.set(true)(new Set()), new Set(['a'])) 55 | U.deepStrictEqual(at.set(true)(new Set(['a', 'b'])), new Set(['a', 'b'])) 56 | U.deepStrictEqual(at.set(false)(new Set(['a', 'b'])), new Set(['b'])) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /examples/introduction.ts: -------------------------------------------------------------------------------- 1 | interface Street { 2 | num: number 3 | name: string 4 | } 5 | interface Address { 6 | city: string 7 | street: Street 8 | } 9 | interface Company { 10 | name: string 11 | address: Address 12 | } 13 | interface Employee { 14 | name: string 15 | company: Company 16 | } 17 | 18 | const employee: Employee = { 19 | name: 'john', 20 | company: { 21 | name: 'awesome inc', 22 | address: { 23 | city: 'london', 24 | street: { 25 | num: 23, 26 | name: 'high street' 27 | } 28 | } 29 | } 30 | } 31 | 32 | function capitalize(s: string): string { 33 | return s.substring(0, 1).toUpperCase() + s.substring(1) 34 | } 35 | 36 | const employee2 = { 37 | ...employee, 38 | company: { 39 | ...employee.company, 40 | address: { 41 | ...employee.company.address, 42 | street: { 43 | ...employee.company.address.street, 44 | name: capitalize(employee.company.address.street.name) 45 | } 46 | } 47 | } 48 | } 49 | 50 | console.log(JSON.stringify(employee2, null, 2)) 51 | 52 | import { Lens, Optional } from '../src' 53 | 54 | const company = Lens.fromProp()('company') 55 | const address = Lens.fromProp()('address') 56 | const street = Lens.fromProp
()('street') 57 | const name = Lens.fromProp()('name') 58 | 59 | import { some, none } from 'fp-ts/lib/Option' 60 | 61 | const firstLetter = new Optional( 62 | (s) => (s.length > 0 ? some(s[0]) : none), 63 | (a) => (s) => a + s.substring(1) 64 | ) 65 | 66 | console.log( 67 | JSON.stringify( 68 | company 69 | .compose(address) 70 | .compose(street) 71 | .compose(name) 72 | .asOptional() 73 | .compose(firstLetter) 74 | .modify((s) => s.toUpperCase())(employee), 75 | null, 76 | 2 77 | ) 78 | ) 79 | -------------------------------------------------------------------------------- /test/2.2/conversions.ts: -------------------------------------------------------------------------------- 1 | import { Iso, Lens, Prism, Optional, Traversal, Getter, Fold, Setter } from '../../src' 2 | import * as assert from 'assert' 3 | 4 | type U0 = true 5 | type U1 = false 6 | 7 | // This test exists to type check the optic conversion functions 8 | 9 | const anIso = new Iso( 10 | () => false, 11 | () => true 12 | ) 13 | 14 | const hasType = (_: T): void => assert(true) 15 | 16 | describe('Conversions', () => { 17 | it('type check', () => { 18 | // Iso conversions 19 | const isoLens: Lens = anIso.asLens() 20 | const isoPrism: Prism = anIso.asPrism() 21 | const isoOptional: Optional = anIso.asOptional() 22 | const isoTraversal: Traversal = anIso.asTraversal() 23 | const isoGetter: Getter = anIso.asGetter() 24 | hasType>(anIso.asFold()) 25 | hasType>(anIso.asSetter()) 26 | 27 | // Lens conversions 28 | hasType>(isoLens.asOptional()) 29 | hasType>(isoLens.asTraversal()) 30 | hasType>(isoLens.asGetter()) 31 | hasType>(isoLens.asFold()) 32 | hasType>(isoLens.asSetter()) 33 | 34 | // Prism conversions 35 | hasType>(isoPrism.asOptional()) 36 | hasType>(isoPrism.asTraversal()) 37 | hasType>(isoPrism.asFold()) 38 | hasType>(isoPrism.asSetter()) 39 | 40 | // Optional conversions 41 | hasType>(isoOptional.asTraversal()) 42 | hasType>(isoOptional.asFold()) 43 | hasType>(isoOptional.asSetter()) 44 | 45 | // Traversal conversions 46 | hasType>(isoTraversal.asFold()) 47 | hasType>(isoTraversal.asSetter()) 48 | 49 | // Getter conversions 50 | hasType>(isoGetter.asFold()) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/2.2/Traversal.ts: -------------------------------------------------------------------------------- 1 | import { Lens, fromTraversable } from '../../src' 2 | import { array } from 'fp-ts/lib/Array' 3 | import { Option, isSome, some, Some } from 'fp-ts/lib/Option' 4 | import * as U from '../util' 5 | 6 | describe('Traversal', () => { 7 | it('fromTraversable', () => { 8 | interface Tweet { 9 | readonly text: string 10 | } 11 | 12 | interface Tweets { 13 | readonly tweets: Array 14 | } 15 | 16 | const tweetsLens = Lens.fromProp()('tweets') 17 | const tweetTextLens = Lens.fromProp()('text') 18 | const tweetTraversal = fromTraversable(array)() 19 | const composedTraversal = tweetsLens.composeTraversal(tweetTraversal).composeLens(tweetTextLens) 20 | 21 | const tweet1: Tweet = { text: 'hello world' } 22 | const tweet2: Tweet = { text: 'foobar' } 23 | const model: Tweets = { tweets: [tweet1, tweet2] } 24 | 25 | const newModel = composedTraversal.modify((text) => text.split('').reverse().join(''))(model) 26 | U.deepStrictEqual(newModel, { tweets: [{ text: 'dlrow olleh' }, { text: 'raboof' }] }) 27 | }) 28 | 29 | it('set', () => { 30 | const traversal = fromTraversable(array)() 31 | U.deepStrictEqual(traversal.set('a')([]), []) 32 | U.deepStrictEqual(traversal.set('a')(['b', 'c']), ['a', 'a']) 33 | }) 34 | 35 | it('filter', () => { 36 | const traversal1 = fromTraversable(array)().filter((s) => s.length > 2) 37 | U.deepStrictEqual(traversal1.set('a')([]), []) 38 | U.deepStrictEqual(traversal1.set('a')(['b', 'c']), ['b', 'c']) 39 | U.deepStrictEqual(traversal1.set('a')(['b', 'foo', 'c']), ['b', 'a', 'c']) 40 | 41 | const traversal2 = fromTraversable(array)>() 42 | .filter(isSome) 43 | .filter((o) => o.value > 2) 44 | U.deepStrictEqual(traversal2.set(some(2) as Some)([]), []) 45 | U.deepStrictEqual(traversal2.set(some(4) as Some)([some(1), some(2), some(3)]), [some(1), some(2), some(4)]) 46 | }) 47 | 48 | it('asFold', () => { 49 | const fold = fromTraversable(array)().asFold() 50 | U.deepStrictEqual(fold.getAll([1, 2, 3]), [1, 2, 3]) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /examples/Lens.ts: -------------------------------------------------------------------------------- 1 | import { Lens } from '../src' 2 | 3 | export interface Street { 4 | num: number 5 | name: string 6 | } 7 | export interface Address { 8 | city: string 9 | street: Street 10 | } 11 | export interface Company { 12 | name: string 13 | address: Address 14 | } 15 | export interface Employee { 16 | name: string 17 | company: Company 18 | } 19 | 20 | export const employee: Employee = { 21 | name: 'john', 22 | company: { 23 | name: 'awesome inc', 24 | address: { 25 | city: 'london', 26 | street: { 27 | num: 23, 28 | name: 'high street' 29 | } 30 | } 31 | } 32 | } 33 | 34 | const company = new Lens( 35 | (s) => s.company, 36 | (a) => (s) => ({ ...s, company: a }) 37 | ) 38 | 39 | console.log(JSON.stringify(company.get(employee), null, 2)) 40 | 41 | const address = new Lens( 42 | (s) => s.address, 43 | (a) => (s) => ({ ...s, address: a }) 44 | ) 45 | const street = new Lens( 46 | (s) => s.street, 47 | (a) => (s) => ({ ...s, street: a }) 48 | ) 49 | const name = new Lens( 50 | (s) => s.name, 51 | (a) => (s) => ({ ...s, name: a }) 52 | ) 53 | 54 | // composition 55 | const streetLens = company.compose(address).compose(street) 56 | export const nameLens = streetLens.compose(name) 57 | 58 | console.log(JSON.stringify(nameLens.get(employee), null, 2)) 59 | 60 | const employee2 = nameLens.modify((a) => a.toUpperCase())(employee) 61 | 62 | console.log(JSON.stringify(employee2, null, 2)) 63 | 64 | const employee3 = nameLens.set('low street')(employee) 65 | 66 | console.log(JSON.stringify(employee3, null, 2)) 67 | 68 | const numLens = streetLens.compose( 69 | new Lens( 70 | (s) => s.num, 71 | (a) => (s) => ({ ...s, num: a }) 72 | ) 73 | ) 74 | 75 | console.log(JSON.stringify(numLens.set(42)(employee), null, 2)) 76 | 77 | // generation 78 | type PersonType = { 79 | name: string 80 | age: number 81 | } 82 | 83 | const person: PersonType = { name: 'Giulio', age: 42 } 84 | 85 | const age = Lens.fromProp()('age') 86 | 87 | console.log(age.set(43)(person)) // => { name: 'Giulio', age: 43 } 88 | -------------------------------------------------------------------------------- /docs/modules/At.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: At.ts 3 | nav_order: 1 4 | parent: Modules 5 | --- 6 | 7 | ## At overview 8 | 9 | **This module is experimental** 10 | 11 | Experimental features are published in order to get early feedback from the community. 12 | 13 | A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 14 | 15 | Added in v2.3.0 16 | 17 | --- 18 | 19 |

Table of contents

20 | 21 | - [constructors](#constructors) 22 | - [at](#at) 23 | - [atReadonlyMap](#atreadonlymap) 24 | - [atReadonlyRecord](#atreadonlyrecord) 25 | - [atReadonlySet](#atreadonlyset) 26 | - [fromIso](#fromiso) 27 | - [~~atRecord~~](#atrecord) 28 | - [model](#model) 29 | - [At (interface)](#at-interface) 30 | 31 | --- 32 | 33 | # constructors 34 | 35 | ## at 36 | 37 | **Signature** 38 | 39 | ```ts 40 | export declare const at: (at: (i: I) => Lens) => At 41 | ``` 42 | 43 | Added in v2.3.8 44 | 45 | ## atReadonlyMap 46 | 47 | **Signature** 48 | 49 | ```ts 50 | export declare const atReadonlyMap: (E: Eq) =>
() => At, K, O.Option> 51 | ``` 52 | 53 | Added in v2.3.7 54 | 55 | ## atReadonlyRecord 56 | 57 | **Signature** 58 | 59 | ```ts 60 | export declare const atReadonlyRecord: () => At>, string, O.Option> 61 | ``` 62 | 63 | Added in v2.3.7 64 | 65 | ## atReadonlySet 66 | 67 | **Signature** 68 | 69 | ```ts 70 | export declare const atReadonlySet: (E: Eq) => At, A, boolean> 71 | ``` 72 | 73 | Added in v2.3.7 74 | 75 | ## fromIso 76 | 77 | Lift an instance of `At` using an `Iso`. 78 | 79 | **Signature** 80 | 81 | ```ts 82 | export declare const fromIso: (iso: Iso) => (sia: At) => At 83 | ``` 84 | 85 | Added in v2.3.0 86 | 87 | ## ~~atRecord~~ 88 | 89 | Use `atReadonlyRecord` instead. 90 | 91 | **Signature** 92 | 93 | ```ts 94 | export declare const atRecord: () => At>, string, O.Option> 95 | ``` 96 | 97 | Added in v2.3.2 98 | 99 | # model 100 | 101 | ## At (interface) 102 | 103 | **Signature** 104 | 105 | ```ts 106 | export interface At { 107 | readonly at: (i: I) => Lens 108 | } 109 | ``` 110 | 111 | Added in v2.3.0 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monocle-ts", 3 | "version": "2.3.13", 4 | "description": "A porting of scala monocle library to TypeScript", 5 | "main": "lib/index.js", 6 | "module": "es6/index.js", 7 | "typings": "lib/index.d.ts", 8 | "sideEffects": false, 9 | "scripts": { 10 | "lint": "tslint -p .", 11 | "jest": "jest", 12 | "jest-coverage": "jest --ci --coverage", 13 | "prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --list-different \"{src,test,examples}/**/*.ts\"", 14 | "fix-prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --write \"{src,test,examples}/**/*.ts\"", 15 | "test": "npm run prettier && npm run lint && npm run dtslint && npm run jest && npm run docs", 16 | "clean": "rm -rf ./dist", 17 | "prebuild": "npm run clean", 18 | "build": "tsc -p ./tsconfig.build.json && tsc -p ./tsconfig.build-es6.json && npm run import-path-rewrite && ts-node scripts/build", 19 | "postbuild": "prettier --loglevel=silent --write \"./dist/**/*.ts\"", 20 | "prepublishOnly": "ts-node scripts/pre-publish", 21 | "docs-fix-prettier": "prettier --no-semi --single-quote --print-width 120 --parser markdown --write \"README.md\"", 22 | "dtslint": "dtslint dtslint", 23 | "mocha": "TS_NODE_CACHE=false mocha -r ts-node/register test/**/*.ts", 24 | "docs": "docs-ts", 25 | "prerelease": "npm run build", 26 | "release": "ts-node scripts/release", 27 | "release-as-next": "ts-node scripts/release-as-next", 28 | "import-path-rewrite": "import-path-rewrite" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/gcanti/monocle-ts.git" 33 | }, 34 | "author": "Giulio Canti ", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/gcanti/monocle-ts/issues" 38 | }, 39 | "homepage": "https://github.com/gcanti/monocle-ts", 40 | "peerDependencies": { 41 | "fp-ts": "^2.5.0" 42 | }, 43 | "devDependencies": { 44 | "@types/benchmark": "^2.1.0", 45 | "@types/glob": "^7.1.3", 46 | "@types/jest": "22.2.2", 47 | "@types/node": "^13.11.0", 48 | "benchmark": "^2.1.4", 49 | "docs-ts": "^0.6.2", 50 | "dtslint": "github:gcanti/dtslint", 51 | "fp-ts": "^2.9.5", 52 | "import-path-rewrite": "github:gcanti/import-path-rewrite", 53 | "jest": "^24.3.0", 54 | "mocha": "^5.2.0", 55 | "optics-ts": "^2.1.0", 56 | "partial.lenses": "^14.17.0", 57 | "prettier": "^2.0.2", 58 | "rimraf": "^2.6.3", 59 | "ts-jest": "^24.0.0", 60 | "ts-node": "^8.0.2", 61 | "tslint": "^5.12.1", 62 | "tslint-config-standard": "^8.0.1", 63 | "tslint-immutable": "^6.0.1", 64 | "typescript": "^4.6.2" 65 | }, 66 | "tags": [ 67 | "typescript", 68 | "functional-programming", 69 | "optics", 70 | "lens", 71 | "fp-ts" 72 | ], 73 | "keywords": [ 74 | "typescript", 75 | "functional-programming", 76 | "optics", 77 | "lens", 78 | "fp-ts" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /test/2.2/At.ts: -------------------------------------------------------------------------------- 1 | import { atRecord } from '../../src/At/Record' 2 | import { atReadonlyRecord } from '../../src/At/ReadonlyRecord' 3 | import { atSet } from '../../src/At/Set' 4 | import { atReadonlySet } from '../../src/At/ReadonlySet' 5 | import * as assert from 'assert' 6 | import { none, some } from 'fp-ts/lib/Option' 7 | import { eqNumber } from 'fp-ts/lib/Eq' 8 | import * as R from 'fp-ts/lib/Record' 9 | import * as S from 'fp-ts/lib/Set' 10 | import { Iso } from '../../src' 11 | import * as U from '../util' 12 | 13 | describe('At', () => { 14 | describe('atRecord', () => { 15 | const map = R.singleton('key', 'value') 16 | const at = atRecord().at('key') 17 | 18 | it('get', () => { 19 | U.deepStrictEqual(at.get(map), some('value')) 20 | }) 21 | 22 | it('add', () => { 23 | const newMap = at.set(some('NEW'))(map) 24 | U.deepStrictEqual(newMap, R.singleton('key', 'NEW')) 25 | }) 26 | 27 | it('delete', () => { 28 | const newMap = at.set(none)(map) 29 | assert(R.isEmpty(newMap)) 30 | }) 31 | }) 32 | 33 | describe('atReadonlyRecord', () => { 34 | const map = R.singleton('key', 'value') 35 | const at = atReadonlyRecord().at('key') 36 | 37 | it('get', () => { 38 | U.deepStrictEqual(at.get(map), some('value')) 39 | }) 40 | 41 | it('add', () => { 42 | const newMap = at.set(some('NEW'))(map) 43 | U.deepStrictEqual(newMap, R.singleton('key', 'NEW')) 44 | }) 45 | 46 | it('delete', () => { 47 | const newMap = at.set(none)(map) 48 | assert(R.isEmpty(newMap)) 49 | }) 50 | }) 51 | 52 | describe('atSet', () => { 53 | const set = S.singleton(3) 54 | const at = atSet(eqNumber).at(3) 55 | 56 | it('get', () => { 57 | U.deepStrictEqual(at.get(set), true) 58 | }) 59 | 60 | it('add', () => { 61 | const newSet = at.set(true)(set) 62 | U.deepStrictEqual(newSet, set) 63 | }) 64 | 65 | it('delete', () => { 66 | const newSet = at.set(false)(set) 67 | U.deepStrictEqual(newSet, new Set()) 68 | }) 69 | }) 70 | 71 | describe('atReadonlySet', () => { 72 | const set = S.singleton(3) 73 | const at = atReadonlySet(eqNumber).at(3) 74 | 75 | it('get', () => { 76 | U.deepStrictEqual(at.get(set), true) 77 | }) 78 | 79 | it('add', () => { 80 | const newSet = at.set(true)(set) 81 | U.deepStrictEqual(newSet, set) 82 | }) 83 | 84 | it('delete', () => { 85 | const newSet = at.set(false)(set) 86 | U.deepStrictEqual(newSet, new Set()) 87 | }) 88 | }) 89 | 90 | it('fromIso', () => { 91 | const iso = new Iso, Record>( 92 | R.map((v) => +v), 93 | R.map(String) 94 | ) 95 | const at = atRecord().fromIso(iso).at('a') 96 | U.deepStrictEqual(at.get({}), none) 97 | U.deepStrictEqual(at.get({ a: '1' }), some(1)) 98 | 99 | U.deepStrictEqual(at.set(none)({}), {}) 100 | U.deepStrictEqual(at.set(some(1))({}), { a: '1' }) 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /docs/modules/Ix.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ix.ts 3 | nav_order: 16 4 | parent: Modules 5 | --- 6 | 7 | ## Ix overview 8 | 9 | **This module is experimental** 10 | 11 | Experimental features are published in order to get early feedback from the community. 12 | 13 | A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 14 | 15 | Added in v2.3.0 16 | 17 | --- 18 | 19 |

Table of contents

20 | 21 | - [constructors](#constructors) 22 | - [fromAt](#fromat) 23 | - [fromIso](#fromiso) 24 | - [index](#index) 25 | - [indexReadonlyArray](#indexreadonlyarray) 26 | - [indexReadonlyMap](#indexreadonlymap) 27 | - [indexReadonlyNonEmptyArray](#indexreadonlynonemptyarray) 28 | - [indexReadonlyRecord](#indexreadonlyrecord) 29 | - [~~indexArray~~](#indexarray) 30 | - [~~indexRecord~~](#indexrecord) 31 | - [model](#model) 32 | - [Index (interface)](#index-interface) 33 | 34 | --- 35 | 36 | # constructors 37 | 38 | ## fromAt 39 | 40 | **Signature** 41 | 42 | ```ts 43 | export declare const fromAt: (at: At>) => Index 44 | ``` 45 | 46 | Added in v2.3.0 47 | 48 | ## fromIso 49 | 50 | Lift an instance of `Index` using an `Iso`. 51 | 52 | **Signature** 53 | 54 | ```ts 55 | export declare const fromIso: (iso: Iso) => (sia: Index) => Index 56 | ``` 57 | 58 | Added in v2.3.0 59 | 60 | ## index 61 | 62 | **Signature** 63 | 64 | ```ts 65 | export declare const index: (index: (i: I) => Optional) => Index 66 | ``` 67 | 68 | Added in v2.3.8 69 | 70 | ## indexReadonlyArray 71 | 72 | **Signature** 73 | 74 | ```ts 75 | export declare const indexReadonlyArray:
() => Index 76 | ``` 77 | 78 | Added in v2.3.7 79 | 80 | ## indexReadonlyMap 81 | 82 | **Signature** 83 | 84 | ```ts 85 | export declare const indexReadonlyMap: (E: Eq) => () => Index, K, A> 86 | ``` 87 | 88 | Added in v2.3.7 89 | 90 | ## indexReadonlyNonEmptyArray 91 | 92 | **Signature** 93 | 94 | ```ts 95 | export declare const indexReadonlyNonEmptyArray: () => Index, number, A> 96 | ``` 97 | 98 | Added in v2.3.8 99 | 100 | ## indexReadonlyRecord 101 | 102 | **Signature** 103 | 104 | ```ts 105 | export declare const indexReadonlyRecord: () => Index>, string, A> 106 | ``` 107 | 108 | Added in v2.3.7 109 | 110 | ## ~~indexArray~~ 111 | 112 | Use `indexReadonlyArray` instead. 113 | 114 | **Signature** 115 | 116 | ```ts 117 | export declare const indexArray: () => Index 118 | ``` 119 | 120 | Added in v2.3.2 121 | 122 | ## ~~indexRecord~~ 123 | 124 | Use `indexReadonlyRecord` instead. 125 | 126 | **Signature** 127 | 128 | ```ts 129 | export declare const indexRecord: () => Index>, string, A> 130 | ``` 131 | 132 | Added in v2.3.2 133 | 134 | # model 135 | 136 | ## Index (interface) 137 | 138 | **Signature** 139 | 140 | ```ts 141 | export interface Index { 142 | readonly index: (i: I) => Optional 143 | } 144 | ``` 145 | 146 | Added in v2.3.0 147 | -------------------------------------------------------------------------------- /src/At.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * **This module is experimental** 3 | * 4 | * Experimental features are published in order to get early feedback from the community. 5 | * 6 | * A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 7 | * 8 | * @since 2.3.0 9 | */ 10 | import { Eq } from 'fp-ts/lib/Eq' 11 | import * as O from 'fp-ts/lib/Option' 12 | import { pipe } from 'fp-ts/lib/pipeable' 13 | import * as RM from 'fp-ts/lib/ReadonlyMap' 14 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 15 | import * as RS from 'fp-ts/lib/ReadonlySet' 16 | import * as _ from './internal' 17 | import { Iso } from './Iso' 18 | import { Lens } from './Lens' 19 | 20 | import Option = O.Option 21 | 22 | // ------------------------------------------------------------------------------------- 23 | // model 24 | // ------------------------------------------------------------------------------------- 25 | 26 | /** 27 | * @category model 28 | * @since 2.3.0 29 | */ 30 | export interface At { 31 | readonly at: (i: I) => Lens 32 | } 33 | 34 | // ------------------------------------------------------------------------------------- 35 | // constructors 36 | // ------------------------------------------------------------------------------------- 37 | 38 | /** 39 | * @category constructors 40 | * @since 2.3.8 41 | */ 42 | export const at: (at: At['at']) => At = _.at 43 | 44 | /** 45 | * Lift an instance of `At` using an `Iso`. 46 | * 47 | * @category constructors 48 | * @since 2.3.0 49 | */ 50 | export const fromIso = (iso: Iso) => (sia: At): At => 51 | at((i) => pipe(iso, _.isoAsLens, _.lensComposeLens(sia.at(i)))) 52 | 53 | /** 54 | * @category constructors 55 | * @since 2.3.7 56 | */ 57 | export const atReadonlyRecord: () => At, string, Option> = _.atReadonlyRecord 58 | 59 | /** 60 | * @category constructors 61 | * @since 2.3.7 62 | */ 63 | export const atReadonlyMap = (E: Eq): (() => At, K, Option>) => { 64 | const lookupE = RM.lookup(E) 65 | const deleteAtE = RM.deleteAt(E) 66 | const insertAtE = RM.insertAt(E) 67 | return () => 68 | at((key) => 69 | _.lens( 70 | (s) => lookupE(key, s), 71 | O.fold( 72 | () => deleteAtE(key), 73 | (a) => insertAtE(key, a) 74 | ) 75 | ) 76 | ) 77 | } 78 | 79 | /** 80 | * @category constructors 81 | * @since 2.3.7 82 | */ 83 | export const atReadonlySet = (E: Eq): At, A, boolean> => { 84 | const elemE = RS.elem(E) 85 | const insertE = RS.insert(E) 86 | const removeE = RS.remove(E) 87 | return at((a) => { 88 | const insert = insertE(a) 89 | const remove = removeE(a) 90 | return _.lens( 91 | (s) => elemE(a, s), 92 | (b) => (s) => (b ? insert(s) : remove(s)) 93 | ) 94 | }) 95 | } 96 | 97 | // ------------------------------------------------------------------------------------- 98 | // deprecated 99 | // ------------------------------------------------------------------------------------- 100 | 101 | /** 102 | * Use `atReadonlyRecord` instead. 103 | * 104 | * @category constructors 105 | * @since 2.3.2 106 | * @deprecated 107 | */ 108 | export const atRecord: () => At, string, Option> = _.atReadonlyRecord 109 | -------------------------------------------------------------------------------- /test/2.2/Prism.ts: -------------------------------------------------------------------------------- 1 | import { Prism } from '../../src' 2 | import { Refinement } from 'fp-ts/lib/function' 3 | import { none, some, Option } from 'fp-ts/lib/Option' 4 | import * as U from '../util' 5 | 6 | interface A { 7 | readonly type: 'A' 8 | readonly a: string 9 | } 10 | interface B { 11 | readonly type: 'B' 12 | readonly b: Option 13 | } 14 | type U = A | B 15 | 16 | const isA: Refinement = (u): u is A => u.type === 'A' 17 | const isB: Refinement = (u): u is B => u.type === 'B' 18 | 19 | describe('Prism', () => { 20 | it('fromPredicate', () => { 21 | const prism = Prism.fromPredicate((n) => n % 1 === 0) 22 | U.deepStrictEqual(prism.getOption(1), some(1)) 23 | U.deepStrictEqual(prism.getOption(1.1), none) 24 | }) 25 | 26 | it('fromRefinement', () => { 27 | const prism = Prism.fromPredicate(isA) 28 | const toUpperCase = (a: A): A => ({ type: 'A', a: a.a.toUpperCase() }) 29 | U.deepStrictEqual(prism.modify(toUpperCase)({ type: 'A', a: 'foo' }), { type: 'A', a: 'FOO' }) 30 | U.deepStrictEqual(prism.modify(toUpperCase)({ type: 'B', b: some(1) }), { type: 'B', b: some(1) }) 31 | }) 32 | 33 | it('some', () => { 34 | const prism = Prism.some() 35 | U.deepStrictEqual(prism.getOption(some(1)), some(1)) 36 | U.deepStrictEqual(prism.getOption(none), none) 37 | U.deepStrictEqual(prism.reverseGet(2), some(2)) 38 | }) 39 | 40 | it('asOptional', () => { 41 | const optional = Prism.some().asOptional() 42 | U.deepStrictEqual(optional.getOption(some(1)), some(1)) 43 | U.deepStrictEqual(optional.getOption(none), none) 44 | U.deepStrictEqual(optional.set(2)(some(1)), some(2)) 45 | U.deepStrictEqual(optional.set(2)(none), none) 46 | }) 47 | 48 | it('asTraversal', () => { 49 | const traversal = Prism.some().asTraversal() 50 | U.deepStrictEqual(traversal.asSetter().set(2)(some(1)), some(2)) 51 | U.deepStrictEqual(traversal.asSetter().set(2)(none), none) 52 | }) 53 | 54 | it('set', () => { 55 | const prism = Prism.some() 56 | U.deepStrictEqual(prism.set(2)(some(1)), some(2)) 57 | U.deepStrictEqual(prism.set(2)(none), none) 58 | }) 59 | 60 | it('modify', () => { 61 | const prism = new Prism( 62 | (s) => (s.type === 'A' ? some(s.a) : none), 63 | (a) => ({ type: 'A', a }) 64 | ) 65 | const toUpperCase = (s: string): string => s.toUpperCase() 66 | U.deepStrictEqual(prism.modify(toUpperCase)({ type: 'A', a: 'foo' }), { type: 'A', a: 'FOO' }) 67 | U.deepStrictEqual(prism.modify(toUpperCase)({ type: 'B', b: some(1) }), { type: 'B', b: some(1) }) 68 | }) 69 | 70 | it('compose', () => { 71 | const prismB = Prism.fromPredicate(isB) 72 | const prism = new Prism( 73 | (s) => s.b, 74 | (b) => ({ type: 'B', b: some(b) }) 75 | ) 76 | const composition1 = prismB.compose(prism) 77 | const composition2 = prismB.composePrism(prism) 78 | U.deepStrictEqual(composition1.getOption({ type: 'B', b: some(1) }), some(1)) 79 | U.deepStrictEqual(composition1.getOption({ type: 'B', b: none }), none) 80 | U.deepStrictEqual(composition1.getOption({ type: 'A', a: 'a' }), none) 81 | U.deepStrictEqual(composition1.reverseGet(1), { type: 'B', b: some(1) }) 82 | 83 | U.deepStrictEqual( 84 | composition2.getOption({ type: 'B', b: some(1) }), 85 | composition1.getOption({ type: 'B', b: some(1) }) 86 | ) 87 | U.deepStrictEqual(composition2.getOption({ type: 'B', b: none }), composition1.getOption({ type: 'B', b: none })) 88 | U.deepStrictEqual(composition2.getOption({ type: 'A', a: 'a' }), composition1.getOption({ type: 'A', a: 'a' })) 89 | U.deepStrictEqual(composition2.reverseGet(1), composition1.reverseGet(1)) 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as E from 'fp-ts/lib/Either' 3 | import { pipe } from 'fp-ts/lib/pipeable' 4 | import * as RTE from 'fp-ts/lib/ReaderTaskEither' 5 | import * as A from 'fp-ts/lib/ReadonlyArray' 6 | import * as TE from 'fp-ts/lib/TaskEither' 7 | import { FileSystem, fileSystem } from './FileSystem' 8 | import { run } from './run' 9 | import * as O from 'fp-ts/lib/Option' 10 | 11 | interface Build extends RTE.ReaderTaskEither {} 12 | 13 | const OUTPUT_FOLDER = 'dist' 14 | const PKG = 'package.json' 15 | 16 | export const copyPackageJson: Build = (C) => 17 | pipe( 18 | C.readFile(PKG), 19 | TE.chain((s) => TE.fromEither(E.parseJSON(s, E.toError))), 20 | TE.map((v) => { 21 | const clone = Object.assign({}, v as any) 22 | 23 | delete clone.scripts 24 | delete clone.files 25 | delete clone.devDependencies 26 | 27 | return clone 28 | }), 29 | TE.chain((json) => C.writeFile(path.join(OUTPUT_FOLDER, PKG), JSON.stringify(json, null, 2))) 30 | ) 31 | 32 | export const FILES: ReadonlyArray = ['CHANGELOG.md', 'LICENSE', 'README.md'] 33 | 34 | export const copyFiles: Build> = (C) => 35 | A.readonlyArray.traverse(TE.taskEither)(FILES, (from) => C.copyFile(from, path.resolve(OUTPUT_FOLDER, from))) 36 | 37 | const traverse = A.readonlyArray.traverse(TE.taskEither) 38 | 39 | export const makeModules: Build = (C) => 40 | pipe( 41 | C.glob(`${OUTPUT_FOLDER}/lib/**/*.js`), 42 | TE.map(getModules), 43 | TE.chain((modules) => traverse(modules, makeSingleModule(C))), 44 | TE.map(() => undefined) 45 | ) 46 | 47 | function getModules(paths: ReadonlyArray): ReadonlyArray]> { 48 | return paths 49 | .map((filePath) => { 50 | const parent = pipe( 51 | path.basename(path.dirname(filePath)), 52 | O.fromPredicate((s) => s !== 'lib') 53 | ) 54 | return [path.basename(filePath, '.js'), parent] as const 55 | }) 56 | .filter((x) => x[0] !== 'index') 57 | } 58 | 59 | function makeSingleModule(C: FileSystem): (module: readonly [string, O.Option]) => TE.TaskEither { 60 | return (module) => { 61 | return pipe( 62 | module[1], 63 | O.fold( 64 | () => 65 | pipe( 66 | C.mkdir(path.join(OUTPUT_FOLDER, module[0])), 67 | TE.chain(() => C.writeFile(path.join(OUTPUT_FOLDER, module[0], 'package.json'), makePkgJson(module))) 68 | ), 69 | (parent) => 70 | pipe( 71 | C.mkdir(path.join(OUTPUT_FOLDER, parent)), 72 | TE.chain(() => C.mkdir(path.join(OUTPUT_FOLDER, parent, module[0]))), 73 | TE.chain(() => 74 | C.writeFile(path.join(OUTPUT_FOLDER, parent, module[0], 'package.json'), makePkgJson(module)) 75 | ) 76 | ) 77 | ) 78 | ) 79 | } 80 | } 81 | 82 | function makePkgJson(module: readonly [string, O.Option]): string { 83 | const name = module[0] 84 | const prefix = pipe( 85 | module[1], 86 | O.fold( 87 | () => '', 88 | () => '../' 89 | ) 90 | ) 91 | const parent = pipe( 92 | module[1], 93 | O.fold( 94 | () => '', 95 | (parent) => parent + '/' 96 | ) 97 | ) 98 | return JSON.stringify( 99 | { 100 | main: `${prefix}../lib/${parent}${name}.js`, 101 | module: `${prefix}../es6/${parent}${name}.js`, 102 | typings: `${prefix}../lib/${parent}${name}.d.ts`, 103 | sideEffects: false 104 | }, 105 | null, 106 | 2 107 | ) 108 | } 109 | 110 | const main: Build = pipe( 111 | copyPackageJson, 112 | RTE.chain(() => copyFiles), 113 | RTE.chain(() => makeModules) 114 | ) 115 | 116 | run( 117 | main({ 118 | ...fileSystem 119 | }) 120 | ) 121 | -------------------------------------------------------------------------------- /src/Ix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * **This module is experimental** 3 | * 4 | * Experimental features are published in order to get early feedback from the community. 5 | * 6 | * A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 7 | * 8 | * @since 2.3.0 9 | */ 10 | import * as O from 'fp-ts/lib/Option' 11 | import { pipe } from 'fp-ts/lib/pipeable' 12 | import { At } from './At' 13 | import * as _ from './internal' 14 | import { Iso } from './Iso' 15 | import { Optional } from './Optional' 16 | import * as RM from 'fp-ts/lib/ReadonlyMap' 17 | import { Eq } from 'fp-ts/lib/Eq' 18 | 19 | import Option = O.Option 20 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 21 | import { ReadonlyNonEmptyArray } from 'fp-ts/lib/ReadonlyNonEmptyArray' 22 | 23 | // ------------------------------------------------------------------------------------- 24 | // model 25 | // ------------------------------------------------------------------------------------- 26 | 27 | /** 28 | * @category model 29 | * @since 2.3.0 30 | */ 31 | export interface Index { 32 | readonly index: (i: I) => Optional 33 | } 34 | 35 | // ------------------------------------------------------------------------------------- 36 | // constructors 37 | // ------------------------------------------------------------------------------------- 38 | 39 | /** 40 | * @category constructors 41 | * @since 2.3.8 42 | */ 43 | export const index: (index: Index['index']) => Index = _.index 44 | 45 | /** 46 | * @category constructors 47 | * @since 2.3.0 48 | */ 49 | export const fromAt = (at: At>): Index => 50 | index((i) => _.lensComposePrism(_.prismSome())(at.at(i))) 51 | 52 | /** 53 | * Lift an instance of `Index` using an `Iso`. 54 | * 55 | * @category constructors 56 | * @since 2.3.0 57 | */ 58 | export const fromIso = (iso: Iso) => (sia: Index): Index => 59 | index((i) => pipe(iso, _.isoAsOptional, _.optionalComposeOptional(sia.index(i)))) 60 | 61 | /** 62 | * @category constructors 63 | * @since 2.3.7 64 | */ 65 | export const indexReadonlyArray: () => Index, number, A> = _.indexReadonlyArray 66 | 67 | /** 68 | * @category constructors 69 | * @since 2.3.8 70 | */ 71 | export const indexReadonlyNonEmptyArray: () => Index, number, A> = 72 | _.indexReadonlyNonEmptyArray 73 | 74 | /** 75 | * @category constructors 76 | * @since 2.3.7 77 | */ 78 | export const indexReadonlyRecord: () => Index, string, A> = _.indexReadonlyRecord 79 | 80 | /** 81 | * @category constructors 82 | * @since 2.3.7 83 | */ 84 | export const indexReadonlyMap = (E: Eq): (() => Index, K, A>) => { 85 | const lookupE = RM.lookup(E) 86 | const insertAtE = RM.insertAt(E) 87 | return () => 88 | index((key) => 89 | _.optional( 90 | (s) => lookupE(key, s), 91 | (next) => { 92 | const insert = insertAtE(key, next) 93 | return (s) => 94 | pipe( 95 | lookupE(key, s), 96 | O.fold( 97 | () => s, 98 | (prev) => (next === prev ? s : insert(s)) 99 | ) 100 | ) 101 | } 102 | ) 103 | ) 104 | } 105 | 106 | // ------------------------------------------------------------------------------------- 107 | // deprecated 108 | // ------------------------------------------------------------------------------------- 109 | 110 | /** 111 | * Use `indexReadonlyArray` instead. 112 | * 113 | * @category constructors 114 | * @since 2.3.2 115 | * @deprecated 116 | */ 117 | export const indexArray: () => Index, number, A> = _.indexReadonlyArray 118 | 119 | /** 120 | * Use `indexReadonlyRecord` instead. 121 | * 122 | * @category constructors 123 | * @since 2.3.2 124 | * @deprecated 125 | */ 126 | export const indexRecord: () => Index, string, A> = _.indexReadonlyRecord 127 | -------------------------------------------------------------------------------- /test/2.2/Lens.ts: -------------------------------------------------------------------------------- 1 | import { Lens } from '../../src' 2 | import * as assert from 'assert' 3 | import { identity } from 'fp-ts/lib/function' 4 | import * as U from '../util' 5 | 6 | interface Street { 7 | readonly num: number 8 | readonly name: string 9 | } 10 | interface Address { 11 | readonly city: string 12 | readonly street: Street 13 | } 14 | interface Company { 15 | readonly name: string 16 | readonly address: Address 17 | } 18 | interface Employee { 19 | readonly name: string 20 | readonly company: Company 21 | } 22 | 23 | const employee: Employee = { 24 | name: 'john', 25 | company: { 26 | name: 'awesome inc', 27 | address: { 28 | city: 'london', 29 | street: { 30 | num: 23, 31 | name: 'high street' 32 | } 33 | } 34 | } 35 | } 36 | 37 | interface Person { 38 | readonly name: string 39 | readonly age: number 40 | readonly rememberMe: boolean 41 | } 42 | 43 | const person: Person = { name: 'giulio', age: 44, rememberMe: true } 44 | 45 | function capitalize(s: string): string { 46 | return s.substring(0, 1).toUpperCase() + s.substring(1) 47 | } 48 | 49 | describe('Lens', () => { 50 | it('fromProp', () => { 51 | const name2 = Lens.fromProp()('name') 52 | assert.strictEqual(name2.get(person), 'giulio') 53 | assert.strictEqual(name2.modify(capitalize)(person).name, 'Giulio') 54 | assert.strictEqual(name2.set('giulio')(person), person) 55 | assert.strictEqual(name2.modify(identity)(person), person) 56 | }) 57 | 58 | it('fromPath', () => { 59 | const lens = Lens.fromPath()(['company', 'address', 'street', 'name']) 60 | assert.strictEqual(lens.modify(capitalize)(employee).company.address.street.name, 'High street') 61 | assert.strictEqual(lens.set('high street')(employee), employee) 62 | assert.strictEqual(lens.modify(identity)(employee), employee) 63 | }) 64 | 65 | it('fromNullableProp', () => { 66 | interface Outer { 67 | readonly inner?: Inner 68 | } 69 | const outer1 = { inner: { value: 1, foo: 'a' } } 70 | 71 | interface Inner { 72 | readonly value: number 73 | readonly foo: string 74 | } 75 | 76 | const value = Lens.fromProp()('value') 77 | const inner = Lens.fromNullableProp()('inner', { value: 0, foo: 'foo' }) 78 | const lens = inner.compose(value) 79 | U.deepStrictEqual(lens.set(1)({}), { inner: { value: 1, foo: 'foo' } }) 80 | assert.strictEqual(lens.get({}), 0) 81 | U.deepStrictEqual(lens.set(1)({ inner: { value: 1, foo: 'bar' } }), { inner: { value: 1, foo: 'bar' } }) 82 | assert.strictEqual(lens.get({ inner: { value: 1, foo: 'bar' } }), 1) 83 | assert.strictEqual(lens.set(1)(outer1), outer1) 84 | assert.strictEqual(lens.modify(identity)(outer1), outer1) 85 | }) 86 | 87 | it('fromProps', () => { 88 | const lens = Lens.fromProps()(['name', 'age']) 89 | U.deepStrictEqual(lens.get(person), { name: 'giulio', age: 44 }) 90 | U.deepStrictEqual(lens.set({ name: 'Guido', age: 47 })(person), { name: 'Guido', age: 47, rememberMe: true }) 91 | assert.strictEqual(lens.set({ age: 44, name: 'giulio' })(person), person) 92 | assert.strictEqual(lens.modify(identity)(person), person) 93 | }) 94 | 95 | it('compose', () => { 96 | const street = Lens.fromProp
()('street') 97 | const name = Lens.fromProp()('name') 98 | const composition1 = street.compose(name) 99 | const composition2 = street.composeLens(name) 100 | const address: Address = { 101 | city: 'city', 102 | street: { 103 | name: 'name', 104 | num: 1 105 | } 106 | } 107 | const expected = { 108 | city: 'city', 109 | street: { 110 | name: 'name2', 111 | num: 1 112 | } 113 | } 114 | assert.strictEqual(composition1.get(address), 'name') 115 | U.deepStrictEqual(composition1.set('name2')(address), expected) 116 | assert.strictEqual(composition1.set('name')(address), address) 117 | assert.strictEqual(composition1.modify(identity)(address), address) 118 | 119 | assert.strictEqual(composition2.get(address), composition1.get(address)) 120 | U.deepStrictEqual(composition2.set('name2')(address), composition1.set('name2')(address)) 121 | assert.strictEqual(composition2.set('name')(address), address) 122 | assert.strictEqual(composition2.modify(identity)(address), address) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | --- 4 | 5 | 6 | 7 | **Table of contents** 8 | 9 | - [Motivation](#motivation) 10 | - [TypeScript compatibility](#typescript-compatibility) 11 | 12 | 13 | 14 | # Motivation 15 | 16 | (Adapted from [monocle site](https://www.optics.dev/Monocle/)) 17 | 18 | Modifying immutable nested object in JavaScript is verbose which makes code difficult to understand and reason about. 19 | 20 | Let's have a look at some examples: 21 | 22 | ```ts 23 | interface Street { 24 | num: number 25 | name: string 26 | } 27 | interface Address { 28 | city: string 29 | street: Street 30 | } 31 | interface Company { 32 | name: string 33 | address: Address 34 | } 35 | interface Employee { 36 | name: string 37 | company: Company 38 | } 39 | ``` 40 | 41 | Let’s say we have an employee and we need to upper case the first character of his company street name. Here is how we 42 | could write it in vanilla JavaScript 43 | 44 | ```ts 45 | const employee: Employee = { 46 | name: 'john', 47 | company: { 48 | name: 'awesome inc', 49 | address: { 50 | city: 'london', 51 | street: { 52 | num: 23, 53 | name: 'high street' 54 | } 55 | } 56 | } 57 | } 58 | 59 | const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1) 60 | 61 | const employee2 = { 62 | ...employee, 63 | company: { 64 | ...employee.company, 65 | address: { 66 | ...employee.company.address, 67 | street: { 68 | ...employee.company.address.street, 69 | name: capitalize(employee.company.address.street.name) 70 | } 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | As we can see copy is not convenient to update nested objects because we need to repeat ourselves. Let's see what could 77 | we do with `monocle-ts` 78 | 79 | ```ts 80 | import { Lens } from 'monocle-ts' 81 | 82 | const company = Lens.fromProp()('company') 83 | const address = Lens.fromProp()('address') 84 | const street = Lens.fromProp
()('street') 85 | const name = Lens.fromProp()('name') 86 | 87 | company 88 | .compose(address) 89 | .compose(street) 90 | .compose(name) 91 | ``` 92 | 93 | `compose` takes two `Lenses`, one from `A` to `B` and another one from `B` to `C` and creates a third `Lens` from `A` to 94 | `C`. Therefore, after composing `company`, `address`, `street` and `name`, we obtain a `Lens` from `Employee` to 95 | `string` (the street name). Now we can use this `Lens` issued from the composition to modify the street name using the 96 | function `capitalize` 97 | 98 | ```ts 99 | company 100 | .compose(address) 101 | .compose(street) 102 | .compose(name) 103 | .modify(capitalize)(employee) 104 | ``` 105 | 106 | You can use the `fromPath` API to avoid some boilerplate 107 | 108 | ```ts 109 | import { Lens } from 'monocle-ts' 110 | 111 | const name = Lens.fromPath()(['company', 'address', 'street', 'name']) 112 | 113 | name.modify(capitalize)(employee) 114 | ``` 115 | 116 | Here `modify` lift a function `string => string` to a function `Employee => Employee`. It works but it would be clearer 117 | if we could zoom into the first character of a `string` with a `Lens`. However, we cannot write such a `Lens` because 118 | `Lenses` require the field they are directed at to be _mandatory_. In our case the first character of a `string` is 119 | optional as a `string` can be empty. So we need another abstraction that would be a sort of partial Lens, in 120 | `monocle-ts` it is called an `Optional`. 121 | 122 | ```ts 123 | import { Optional } from 'monocle-ts' 124 | import { some, none } from 'fp-ts/lib/Option' 125 | 126 | const firstLetter = new Optional(s => (s.length > 0 ? some(s[0]) : none), a => s => a + s.substring(1)) 127 | 128 | company 129 | .compose(address) 130 | .compose(street) 131 | .compose(name) 132 | .asOptional() 133 | .compose(firstLetter) 134 | .modify(s => s.toUpperCase())(employee) 135 | ``` 136 | 137 | Similarly to `compose` for lenses, `compose` for optionals takes two `Optionals`, one from `A` to `B` and another from 138 | `B` to `C` and creates a third `Optional` from `A` to `C`. All `Lenses` can be seen as `Optionals` where the optional 139 | element to zoom into is always present, hence composing an `Optional` and a `Lens` always produces an `Optional`. 140 | 141 | # TypeScript compatibility 142 | 143 | The stable version is tested against TypeScript 3.2.2, but should run with TypeScript 2.8.0+ too 144 | 145 | **Note**. If you are running `< typescript@3.0.1` you have to polyfill `unknown`. 146 | 147 | You can use [unknown-ts](https://github.com/gcanti/unknown-ts) as a polyfill. 148 | -------------------------------------------------------------------------------- /dtslint/ts3.5/index.ts: -------------------------------------------------------------------------------- 1 | import { Lens, Optional, fromFoldable } from '../../src' 2 | import { Option } from 'fp-ts/lib/Option' 3 | import { array } from 'fp-ts/lib/Array' 4 | 5 | interface Person { 6 | name: string 7 | age: number 8 | rememberMe: boolean 9 | email: Option 10 | bio?: string 11 | a: { 12 | b: { 13 | c: { 14 | d: number 15 | } 16 | } 17 | } 18 | } 19 | 20 | type ConstrainedRecord = { [K in 'a' | 'b']: number } 21 | 22 | // 23 | // OptionPropertyNames 24 | // 25 | 26 | type OptionPropertyNames = { [K in keyof S]-?: S[K] extends Option ? K : never }[keyof S] 27 | 28 | type Options = OptionPropertyNames // $ExpectType "email" 29 | 30 | // 31 | // Lens.fromProp 32 | // 33 | 34 | Lens.fromProp()('name') // $ExpectType Lens 35 | Lens.fromProp()('a') // $ExpectType Lens 36 | 37 | // $ExpectError 38 | Lens.fromProp()(['foo']) 39 | 40 | // 41 | // Lens.fromProps 42 | // 43 | 44 | Lens.fromProps()(['name', 'age']) // $ExpectType Lens 45 | const getLensFromProps = (): Lens => Lens.fromProps()(['name']) 46 | Lens.fromProps()(['a', 'b']) // $ExpectType Lens 47 | 48 | // $ExpectError 49 | Lens.fromProps()(['foo']) 50 | 51 | // 52 | // Lens.fromPath 53 | // 54 | 55 | interface FromPathBad { 56 | a: { 57 | b: { 58 | c: { 59 | [key: string]: number 60 | } 61 | d: Array 62 | } 63 | } 64 | } 65 | 66 | Lens.fromPath()(['a', 'b', 'c', 'd']) // $ExpectType Lens 67 | const getLensFromPath = (): Lens => Lens.fromPath()(['a', 'b', 'c', 'd']) 68 | 69 | // 70 | // Lens.fromNullableProp 71 | // 72 | 73 | Lens.fromNullableProp()('bio', 'foo') // $ExpectType Lens 74 | const getLensFromNullableProp = (): Lens> => 75 | Lens.fromNullableProp()('bio', 'foo') 76 | 77 | // 78 | // Optional.fromNullableProp 79 | // 80 | 81 | Optional.fromNullableProp()('bio') // $ExpectType Optional 82 | const getOptionalFromNullableProp = (): Optional> => 83 | Optional.fromNullableProp()('bio') 84 | 85 | // 86 | // Optional.fromPath 87 | // 88 | 89 | Optional.fromPath()(['bio']) // $ExpectType Optional 90 | 91 | // 92 | // Optional.fromOptionProp 93 | // 94 | 95 | Optional.fromOptionProp()('email') // $ExpectType Optional 96 | // const getOptionalFromOptionProp = (): Optional => Optional.fromOptionProp('email') 97 | 98 | // $ExpectError 99 | Optional.fromOptionProp(undefined) 100 | // $ExpectError 101 | Optional.fromOptionProp()(undefined) 102 | // $ExpectError 103 | Optional.fromOptionProp()('name') // 'name' exists but is not of type Option 104 | // $ExpectError 105 | Optional.fromOptionProp('name') 106 | // $ExpectError 107 | Optional.fromOptionProp()('foo') // 'foo' is not a property of Person 108 | // $ExpectError 109 | Optional.fromOptionProp('foo') 110 | 111 | interface Phone { 112 | number: string 113 | } 114 | interface Employment { 115 | phone: Option 116 | } 117 | 118 | Optional.fromOptionProp()('phone') // $ExpectType Optional 119 | // $ExpectError 120 | Optional.fromOptionProp()('foo') 121 | 122 | interface A { 123 | type: 'A' 124 | } 125 | interface B { 126 | type: 'B' 127 | } 128 | 129 | const fold = fromFoldable(array)() 130 | const isB = (x: A | B): x is B => x.type === 'B' 131 | const s: Array = [] 132 | 133 | // Fold find method 134 | fold.find(isB)(s) // $ExpectType Option 135 | 136 | // 137 | // At 138 | // 139 | 140 | import * as ARR from '../../src/At/ReadonlyRecord' 141 | import * as RS from '../../src/At/ReadonlySet' 142 | import { eqString } from 'fp-ts/lib/Eq' 143 | 144 | ARR.atReadonlyRecord().at('a') // $ExpectType Lens>, Option> 145 | 146 | RS.atReadonlySet(eqString).at('a') // $ExpectType Lens, boolean> 147 | 148 | // 149 | // Index 150 | // 151 | 152 | import * as RA from '../../src/Index/ReadonlyArray' 153 | import * as RNEA from '../../src/Index/ReadonlyNonEmptyArray' 154 | import * as IRR from '../../src/Index/ReadonlyRecord' 155 | 156 | RA.indexReadonlyArray().index(0) // $ExpectType Optional 157 | 158 | RNEA.indexReadonlyNonEmptyArray().index(0) // $ExpectType Optional, string> 159 | 160 | IRR.indexReadonlyRecord().index('a') // $ExpectType Optional>, string> 161 | -------------------------------------------------------------------------------- /test/2.2/Optional.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import { identity } from 'fp-ts/lib/function' 3 | import * as O from 'fp-ts/lib/Option' 4 | import { Optional } from '../../src' 5 | import * as U from '../util' 6 | 7 | describe('Optional', () => { 8 | interface Phone { 9 | readonly number: string 10 | } 11 | interface Employment { 12 | readonly phone?: Phone 13 | } 14 | interface Info { 15 | readonly employment?: Employment 16 | } 17 | interface Response { 18 | readonly info?: Info 19 | } 20 | 21 | it('fromPath', () => { 22 | const response1: Response = { 23 | info: { 24 | employment: { 25 | phone: { 26 | number: '555-1234' 27 | } 28 | } 29 | } 30 | } 31 | const response2: Response = { 32 | info: { 33 | employment: {} 34 | } 35 | } 36 | 37 | const numberFromResponse = Optional.fromPath()(['info', 'employment', 'phone', 'number']) 38 | 39 | U.deepStrictEqual(numberFromResponse.getOption(response1), O.some('555-1234')) 40 | U.deepStrictEqual(numberFromResponse.getOption(response2), O.none) 41 | U.deepStrictEqual(numberFromResponse.set('b')(response1), { 42 | info: { 43 | employment: { 44 | phone: { 45 | number: 'b' 46 | } 47 | } 48 | } 49 | }) 50 | U.deepStrictEqual(numberFromResponse.set('b')(response2), response2) 51 | assert.strictEqual(numberFromResponse.set('555-1234')(response1), response1) 52 | assert.strictEqual(numberFromResponse.modify(identity)(response1), response1) 53 | assert.strictEqual(numberFromResponse.modify(identity)(response2), response2) 54 | }) 55 | 56 | it('fromNullableProp', () => { 57 | // -------------------------------------------------------- 58 | // laws 59 | // -------------------------------------------------------- 60 | 61 | // 62 | // 2. 63 | // 64 | 65 | interface S { 66 | readonly a: number | undefined | null 67 | } 68 | const optional = Optional.fromNullableProp()('a') 69 | const s1: S = { a: undefined } 70 | const s2: S = { a: null } 71 | const s3: S = { a: 1 } 72 | U.deepStrictEqual(optional.set(2)(s1), s1) 73 | U.deepStrictEqual(optional.set(2)(s2), s2) 74 | U.deepStrictEqual(optional.set(2)(s3), { a: 2 }) 75 | }) 76 | 77 | it('fromOptionProp', () => { 78 | interface S { 79 | readonly a: O.Option 80 | } 81 | const optional = Optional.fromOptionProp()('a') 82 | const s1: S = { a: O.none } 83 | const s2: S = { a: O.some(1) } 84 | U.deepStrictEqual(optional.set(2)(s1), s1) 85 | U.deepStrictEqual(optional.set(2)(s2), { a: O.some(2) }) 86 | }) 87 | 88 | it('modify', () => { 89 | interface S { 90 | readonly a: O.Option 91 | } 92 | const optional = Optional.fromOptionProp()('a') 93 | const double = (n: number): number => n * 2 94 | U.deepStrictEqual(optional.modify(double)({ a: O.some(2) }), { a: O.some(4) }) 95 | U.deepStrictEqual(optional.modify(double)({ a: O.none }), { a: O.none }) 96 | }) 97 | 98 | it('compose', () => { 99 | interface Phone { 100 | readonly number: O.Option 101 | } 102 | interface Employment { 103 | readonly phone: O.Option 104 | } 105 | const phone = Optional.fromOptionProp()('phone') 106 | const phoneNumber = Optional.fromOptionProp()('number') 107 | const composition1 = phone.compose(phoneNumber) 108 | const composition2 = phone.composeOptional(phoneNumber) 109 | U.deepStrictEqual(composition1.getOption({ phone: O.none }), O.none) 110 | U.deepStrictEqual(composition1.getOption({ phone: O.some({ number: O.none }) }), O.none) 111 | U.deepStrictEqual(composition1.getOption({ phone: O.some({ number: O.some('a') }) }), O.some('a')) 112 | U.deepStrictEqual(composition1.set('a')({ phone: O.none }), { phone: O.none }) 113 | U.deepStrictEqual(composition1.set('a')({ phone: O.some({ number: O.none }) }), { 114 | phone: O.some({ number: O.none }) 115 | }) 116 | U.deepStrictEqual(composition1.set('a')({ phone: O.some({ number: O.some('b') }) }), { 117 | phone: O.some({ number: O.some('a') }) 118 | }) 119 | 120 | U.deepStrictEqual(composition2.getOption({ phone: O.none }), composition1.getOption({ phone: O.none })) 121 | U.deepStrictEqual( 122 | composition2.getOption({ phone: O.some({ number: O.none }) }), 123 | composition1.getOption({ phone: O.some({ number: O.none }) }) 124 | ) 125 | U.deepStrictEqual( 126 | composition2.getOption({ phone: O.some({ number: O.some('a') }) }), 127 | composition1.getOption({ phone: O.some({ number: O.some('a') }) }) 128 | ) 129 | 130 | U.deepStrictEqual(composition2.set('a')({ phone: O.none }), composition1.set('a')({ phone: O.none })) 131 | U.deepStrictEqual( 132 | composition2.set('a')({ phone: O.some({ number: O.none }) }), 133 | composition1.set('a')({ phone: O.some({ number: O.none }) }) 134 | ) 135 | U.deepStrictEqual( 136 | composition2.set('a')({ phone: O.some({ number: O.some('b') }) }), 137 | composition1.set('a')({ phone: O.some({ number: O.some('b') }) }) 138 | ) 139 | }) 140 | }) 141 | -------------------------------------------------------------------------------- /test/Traversal.ts: -------------------------------------------------------------------------------- 1 | import * as _ from '../src/Traversal' 2 | import * as A from 'fp-ts/lib/ReadonlyArray' 3 | import * as Id from 'fp-ts/lib/Identity' 4 | import * as O from 'fp-ts/lib/Option' 5 | import { pipe } from 'fp-ts/lib/pipeable' 6 | import { monoidSum } from 'fp-ts/lib/Monoid' 7 | import * as U from './util' 8 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 9 | import * as RNEA from 'fp-ts/lib/ReadonlyNonEmptyArray' 10 | 11 | describe('Traversal', () => { 12 | describe('instances', () => { 13 | it('compose', () => { 14 | const sa = _.fromTraversable(A.readonlyArray)>() 15 | const ab = _.fromTraversable(A.readonlyArray)() 16 | const sb = _.Category.compose(ab, sa) 17 | U.deepStrictEqual(sb.modifyF(Id.identity)((n) => n * 2)([[1], [2], [3]]), [[2], [4], [6]]) 18 | }) 19 | }) 20 | 21 | it('fromTraversable', () => { 22 | const sa = _.fromTraversable(A.readonlyArray)() 23 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)([1, 2, 3]), [2, 4, 6]) 24 | }) 25 | 26 | it('id', () => { 27 | const ss = _.id>() 28 | U.deepStrictEqual(ss.modifyF(Id.identity)((ns) => ns.map((n) => n * 2))([1, 2, 3]), [2, 4, 6]) 29 | }) 30 | 31 | it('prop', () => { 32 | const sa = pipe( 33 | _.fromTraversable(A.readonlyArray)<{ 34 | readonly a: string 35 | readonly b: number 36 | readonly c: boolean 37 | }>(), 38 | _.prop('b') 39 | ) 40 | U.deepStrictEqual( 41 | sa.modifyF(Id.identity)((n) => n * 2)([ 42 | { a: 'a', b: 1, c: true }, 43 | { a: 'aa', b: 2, c: false }, 44 | { a: 'aaa', b: 3, c: true } 45 | ]), 46 | [ 47 | { a: 'a', b: 2, c: true }, 48 | { a: 'aa', b: 4, c: false }, 49 | { a: 'aaa', b: 6, c: true } 50 | ] 51 | ) 52 | }) 53 | 54 | it('props', () => { 55 | const sa = pipe( 56 | _.fromTraversable(A.readonlyArray)<{ 57 | readonly a: string 58 | readonly b: number 59 | readonly c: boolean 60 | }>(), 61 | _.props('a', 'b') 62 | ) 63 | U.deepStrictEqual( 64 | sa.modifyF(Id.identity)((x) => ({ a: x.a, b: x.b * 2 }))([ 65 | { a: 'a', b: 1, c: true }, 66 | { a: 'aa', b: 2, c: false }, 67 | { a: 'aaa', b: 3, c: true } 68 | ]), 69 | [ 70 | { a: 'a', b: 2, c: true }, 71 | { a: 'aa', b: 4, c: false }, 72 | { a: 'aaa', b: 6, c: true } 73 | ] 74 | ) 75 | }) 76 | 77 | it('component', () => { 78 | const sa = pipe(_.fromTraversable(A.readonlyArray)(), _.component(1)) 79 | U.deepStrictEqual( 80 | sa.modifyF(Id.identity)((n) => n * 2)([ 81 | ['a', 1], 82 | ['b', 2], 83 | ['c', 3] 84 | ]), 85 | [ 86 | ['a', 2], 87 | ['b', 4], 88 | ['c', 6] 89 | ] 90 | ) 91 | }) 92 | 93 | it('index', () => { 94 | const sa = pipe(_.id>(), _.index(0)) 95 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)([]), []) 96 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)([1, 2, 3]), [2, 2, 3]) 97 | }) 98 | 99 | it('indexNonEmpty', () => { 100 | const sa = pipe(_.id>(), _.indexNonEmpty(1)) 101 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)([1]), [1]) 102 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)([1, 2, 3]), [1, 4, 3]) 103 | }) 104 | 105 | it('key', () => { 106 | const sa = pipe(_.id>(), _.key('k')) 107 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)({ k: 1, j: 2 }), { k: 2, j: 2 }) 108 | }) 109 | 110 | it('atKey', () => { 111 | const sa = pipe(_.id>(), _.atKey('k')) 112 | const f = sa.modifyF(Id.identity)((on) => 113 | pipe( 114 | on, 115 | O.filter((n) => n > 0), 116 | O.map((n) => n * 2) 117 | ) 118 | ) 119 | U.deepStrictEqual(f({ k: 1, j: 2 }), { k: 2, j: 2 }) 120 | U.deepStrictEqual(f({ k: 0, j: 2 }), { j: 2 }) 121 | }) 122 | 123 | it('traverse', () => { 124 | const sa = pipe(_.id>(), _.traverse(A.readonlyArray)) 125 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)([1, 2, 3]), [2, 4, 6]) 126 | }) 127 | 128 | it('fold', () => { 129 | const sa = pipe(_.id>(), _.traverse(A.readonlyArray)) 130 | const f = pipe(sa, _.fold(monoidSum)) 131 | U.deepStrictEqual(f([1, 2, 3]), 6) 132 | }) 133 | 134 | it('getAll', () => { 135 | const as: ReadonlyArray = [1, 2, 3] 136 | const sa = pipe(_.id>(), _.traverse(A.readonlyArray)) 137 | U.deepStrictEqual(pipe(sa, _.getAll(as)), [1, 2, 3]) 138 | }) 139 | 140 | it('findFirst', () => { 141 | type S = ReadonlyArray 142 | const optional = pipe( 143 | _.id(), 144 | _.findFirst((n) => n > 0) 145 | ) 146 | U.deepStrictEqual(optional.modifyF(Id.identity)((n) => n * 2)([-1, 2, 3]), [-1, 4, 3]) 147 | }) 148 | 149 | it('findFirstNonEmpty', () => { 150 | type S = RNEA.ReadonlyNonEmptyArray 151 | const optional = pipe( 152 | _.id(), 153 | _.findFirstNonEmpty((n) => n > 0) 154 | ) 155 | U.deepStrictEqual(optional.modifyF(Id.identity)((n) => n * 2)([-1, 2, 3]), [-1, 4, 3]) 156 | }) 157 | 158 | it('fromNullable', () => { 159 | type S = RNEA.ReadonlyNonEmptyArray 160 | const sa = pipe(_.id(), _.traverse(RNEA.Traversable), _.fromNullable) 161 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n * 2)([1, undefined, 3]), [2, undefined, 6]) 162 | }) 163 | }) 164 | -------------------------------------------------------------------------------- /perf/119.ts: -------------------------------------------------------------------------------- 1 | import * as Benchmark from 'benchmark' 2 | import { fromTraversable, Lens, Prism } from '../src' 3 | import * as L from '../src/Lens' 4 | import * as T from '../src/Traversal' 5 | import { Traversable } from 'fp-ts/ReadonlyArray' 6 | import { pipe } from 'fp-ts/function' 7 | const PL = require('partial.lenses') 8 | import * as O from 'optics-ts' 9 | 10 | /* 11 | size = 5000 12 | prism into array (monocle-ts, stable APIs) x 1,123 ops/sec ±0.37% (87 runs sampled) 13 | prism into array (monocle-ts, experimental APIs) x 951 ops/sec ±2.18% (86 runs sampled) 14 | prism into array (partial.lenses) x 451,070 ops/sec ±1.17% (89 runs sampled) 15 | prism into array (optics-ts) x 10,672 ops/sec ±2.82% (84 runs sampled) 16 | prism modify array (monocle-ts, stable APIs) x 1,336 ops/sec ±0.61% (89 runs sampled) 17 | prism modify array (monocle-ts, experimental APIs) x 1,251 ops/sec ±2.06% (87 runs sampled) 18 | prism modify array (partial.lenses) x 2,191 ops/sec ±2.12% (81 runs sampled) 19 | prism modify array (optics-ts) x 3,763 ops/sec ±0.51% (92 runs sampled) 20 | 21 | size = 500 22 | prism into array (monocle-ts, stable APIs) x 10,328 ops/sec ±7.12% (87 runs sampled) 23 | prism into array (monocle-ts, experimental APIs) x 9,230 ops/sec ±0.41% (91 runs sampled) 24 | prism into array (partial.lenses) x 466,256 ops/sec ±0.49% (86 runs sampled) 25 | prism into array (optics-ts) x 105,339 ops/sec ±0.45% (94 runs sampled) 26 | prism modify array (monocle-ts, stable APIs) x 13,290 ops/sec ±1.53% (82 runs sampled) 27 | prism modify array (monocle-ts, experimental APIs) x 12,911 ops/sec ±0.64% (88 runs sampled) 28 | prism modify array (partial.lenses) x 30,039 ops/sec ±0.45% (91 runs sampled) 29 | prism modify array (optics-ts) x 37,777 ops/sec ±0.46% (90 runs sampled) 30 | 31 | size = 50 32 | prism into array (monocle-ts, stable APIs) x 95,180 ops/sec ±10.31% (89 runs sampled) 33 | prism into array (monocle-ts, experimental APIs) x 87,631 ops/sec ±0.39% (90 runs sampled) 34 | prism into array (partial.lenses) x 462,813 ops/sec ±0.27% (90 runs sampled) 35 | prism into array (optics-ts) x 558,323 ops/sec ±0.46% (91 runs sampled) 36 | prism modify array (monocle-ts, stable APIs) x 111,160 ops/sec ±0.63% (88 runs sampled) 37 | prism modify array (monocle-ts, experimental APIs) x 113,941 ops/sec ±0.59% (86 runs sampled) 38 | prism modify array (partial.lenses) x 200,423 ops/sec ±0.56% (87 runs sampled) 39 | prism modify array (optics-ts) x 251,464 ops/sec ±0.37% (90 runs sampled) 40 | */ 41 | 42 | const suite = new Benchmark.Suite() 43 | 44 | const size = 5000 45 | const mid = Math.floor(size / 2) 46 | const id = 'id-' + mid 47 | 48 | interface Child { 49 | readonly id: string 50 | readonly name: string 51 | } 52 | 53 | const makeNames = (): ReadonlyArray => { 54 | const arr: Array = [] 55 | for (let i = 0; i < size; i++) { 56 | arr.push({ 57 | id: 'id-' + i, 58 | name: 'Luke-' + i 59 | }) 60 | } 61 | return arr 62 | } 63 | 64 | const data = { 65 | a: { 66 | b: { 67 | c: { d: { e: 'hello' } } 68 | } 69 | }, 70 | m: { 71 | n: { 72 | names: makeNames() 73 | } 74 | } 75 | } as const 76 | 77 | const traversalMonocleStable = Lens.fromPath()(['m', 'n', 'names']) 78 | .composeTraversal(fromTraversable(Traversable)()) 79 | .composePrism(Prism.fromPredicate((child: Child) => child.id === id)) 80 | 81 | const foldMonocleStable = traversalMonocleStable.asFold() 82 | 83 | const traversalMonocleExperimental = pipe( 84 | L.id(), 85 | L.prop('m'), 86 | L.prop('n'), 87 | L.prop('names'), 88 | L.traverse(Traversable), 89 | T.filter((child) => child.id === id) 90 | ) 91 | 92 | const traversalPartialLenses = PL.compose( 93 | PL.prop('m'), 94 | PL.prop('n'), 95 | PL.prop('names'), 96 | PL.find((child: Child) => child.id === id) 97 | ) 98 | 99 | const foldPartialLenses = PL.compose(traversalPartialLenses, PL.valueOr(undefined)) 100 | 101 | const traversalMonocleExperimentalModify = pipe( 102 | traversalMonocleExperimental, 103 | T.modify((s) => ({ ...s, name: nameModified })) 104 | ) 105 | 106 | const traversalOpticsTs = O.optic() 107 | .prop('m') 108 | .prop('n') 109 | .prop('names') 110 | .elems() 111 | // `item` here has type: `NotAnArrayType`, not sure what it means 112 | .when((item: any) => item.id === id) 113 | 114 | const nameModified = 'Luke-' + mid + '-modified' 115 | const f = (c: Child): Child => ({ ...c, name: nameModified }) 116 | 117 | suite 118 | .add('prism into array (monocle-ts, stable APIs)', function () { 119 | foldMonocleStable.headOption(data) 120 | }) 121 | .add('prism into array (monocle-ts, experimental APIs)', function () { 122 | pipe(traversalMonocleExperimental, T.getAll(data)) 123 | }) 124 | .add('prism into array (partial.lenses)', function () { 125 | PL.get(foldPartialLenses, data) 126 | }) 127 | .add('prism into array (optics-ts)', function () { 128 | O.preview(traversalOpticsTs)(data) 129 | }) 130 | .add('prism modify array (monocle-ts, stable APIs)', function () { 131 | traversalMonocleStable.modify(f)(data) 132 | }) 133 | .add('prism modify array (monocle-ts, experimental APIs)', function () { 134 | traversalMonocleExperimentalModify(data) 135 | }) 136 | .add('prism modify array (partial.lenses)', function () { 137 | PL.modify(traversalPartialLenses, f, data) 138 | }) 139 | .add('prism modify array (optics-ts)', function () { 140 | O.modify(traversalOpticsTs)(f as any)(data) 141 | }) 142 | .on('cycle', function (event: any) { 143 | // tslint:disable-next-line: no-console 144 | console.log(String(event.target)) 145 | }) 146 | .on('complete', function (this: any) { 147 | // tslint:disable-next-line: no-console 148 | console.log('Fastest is ' + this.filter('fastest').map('name')) 149 | }) 150 | .run({ async: true }) 151 | -------------------------------------------------------------------------------- /test/2.2/Index.ts: -------------------------------------------------------------------------------- 1 | import { indexArray } from '../../src/Index/Array' 2 | import { indexReadonlyArray } from '../../src/Index/ReadonlyArray' 3 | import { indexNonEmptyArray } from '../../src/Index/NonEmptyArray' 4 | import { indexReadonlyNonEmptyArray } from '../../src/Index/ReadonlyNonEmptyArray' 5 | import { indexRecord } from '../../src/Index/Record' 6 | import { indexReadonlyRecord } from '../../src/Index/ReadonlyRecord' 7 | import { some, none } from 'fp-ts/lib/Option' 8 | import * as R from 'fp-ts/lib/Record' 9 | import { cons } from 'fp-ts/lib/NonEmptyArray' 10 | import { Iso } from '../../src' 11 | import * as U from '../util' 12 | 13 | describe('Index', () => { 14 | describe('indexRecord', () => { 15 | const index = indexRecord().index('key') 16 | 17 | it('get', () => { 18 | const map = R.singleton('key', 'value') 19 | U.deepStrictEqual(index.getOption(map), some('value')) 20 | }) 21 | 22 | it('set if there', () => { 23 | const map = R.singleton('key', 'value') 24 | const newMap = index.set('new')(map) 25 | U.deepStrictEqual(newMap, R.singleton('key', 'new')) 26 | }) 27 | 28 | it('leave if missing', () => { 29 | const map = {} 30 | const newMap = index.set('new')(map) 31 | U.deepStrictEqual(newMap, map) 32 | }) 33 | }) 34 | 35 | describe('indexReadonlyRecord', () => { 36 | const index = indexReadonlyRecord().index('key') 37 | 38 | it('get', () => { 39 | const map = R.singleton('key', 'value') 40 | U.deepStrictEqual(index.getOption(map), some('value')) 41 | }) 42 | 43 | it('set if there', () => { 44 | const map = R.singleton('key', 'value') 45 | const newMap = index.set('new')(map) 46 | U.deepStrictEqual(newMap, R.singleton('key', 'new')) 47 | }) 48 | 49 | it('leave if missing', () => { 50 | const map = {} 51 | const newMap = index.set('new')(map) 52 | U.deepStrictEqual(newMap, map) 53 | }) 54 | }) 55 | 56 | describe('indexArray', () => { 57 | const one = indexArray().index(1) 58 | 59 | it('get', () => { 60 | U.deepStrictEqual(one.getOption(['a']), none) 61 | U.deepStrictEqual(one.getOption(['a', 'b']), some('b')) 62 | }) 63 | 64 | it('get', () => { 65 | U.deepStrictEqual(one.set('x')(['a']), ['a']) 66 | U.deepStrictEqual(one.set('x')(['a', 'b']), ['a', 'x']) 67 | }) 68 | 69 | it('modify', () => { 70 | U.deepStrictEqual(one.modify((v) => `${v}X`)(['a']), ['a']) 71 | U.deepStrictEqual(one.modify((v) => `${v}X`)(['a', 'b']), ['a', 'bX']) 72 | }) 73 | 74 | it('modifyOption', () => { 75 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(['a']), none) 76 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(['a', 'b']), some(['a', 'bX'])) 77 | }) 78 | }) 79 | 80 | describe('indexReadonlyArray', () => { 81 | const one = indexReadonlyArray().index(1) 82 | 83 | it('get', () => { 84 | U.deepStrictEqual(one.getOption(['a']), none) 85 | U.deepStrictEqual(one.getOption(['a', 'b']), some('b')) 86 | }) 87 | 88 | it('get', () => { 89 | U.deepStrictEqual(one.set('x')(['a']), ['a']) 90 | U.deepStrictEqual(one.set('x')(['a', 'b']), ['a', 'x']) 91 | }) 92 | 93 | it('modify', () => { 94 | U.deepStrictEqual(one.modify((v) => `${v}X`)(['a']), ['a']) 95 | U.deepStrictEqual(one.modify((v) => `${v}X`)(['a', 'b']), ['a', 'bX']) 96 | }) 97 | 98 | it('modifyOption', () => { 99 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(['a']), none) 100 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(['a', 'b']), some(['a', 'bX'])) 101 | }) 102 | }) 103 | 104 | describe('indexNonEmptyArray', () => { 105 | const one = indexNonEmptyArray().index(1) 106 | 107 | it('get', () => { 108 | U.deepStrictEqual(one.getOption(cons('a', [])), none) 109 | U.deepStrictEqual(one.getOption(cons('a', ['b'])), some('b')) 110 | }) 111 | 112 | it('get', () => { 113 | U.deepStrictEqual(one.set('x')(cons('a', [])), cons('a', [])) 114 | U.deepStrictEqual(one.set('x')(cons('a', ['b'])), cons('a', ['x'])) 115 | }) 116 | 117 | it('modify', () => { 118 | U.deepStrictEqual(one.modify((v) => `${v}X`)(cons('a', [])), cons('a', [])) 119 | U.deepStrictEqual(one.modify((v) => `${v}X`)(cons('a', ['b'])), cons('a', ['bX'])) 120 | }) 121 | 122 | it('modifyOption', () => { 123 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(cons('a', [])), none) 124 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(cons('a', ['b'])), some(cons('a', ['bX']))) 125 | }) 126 | }) 127 | 128 | describe('indexReadonlyNonEmptyArray', () => { 129 | const one = indexReadonlyNonEmptyArray().index(1) 130 | 131 | it('get', () => { 132 | U.deepStrictEqual(one.getOption(cons('a', [])), none) 133 | U.deepStrictEqual(one.getOption(cons('a', ['b'])), some('b')) 134 | }) 135 | 136 | it('get', () => { 137 | U.deepStrictEqual(one.set('x')(cons('a', [])), cons('a', [])) 138 | U.deepStrictEqual(one.set('x')(cons('a', ['b'])), cons('a', ['x'])) 139 | }) 140 | 141 | it('modify', () => { 142 | U.deepStrictEqual(one.modify((v) => `${v}X`)(cons('a', [])), cons('a', [])) 143 | U.deepStrictEqual(one.modify((v) => `${v}X`)(cons('a', ['b'])), cons('a', ['bX'])) 144 | }) 145 | 146 | it('modifyOption', () => { 147 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(cons('a', [])), none) 148 | U.deepStrictEqual(one.modifyOption((v) => `${v}X`)(cons('a', ['b'])), some(cons('a', ['bX']))) 149 | }) 150 | }) 151 | 152 | it('fromIso', () => { 153 | const iso = new Iso, Array>( 154 | (s) => s.map((v) => +v), 155 | (a) => a.map(String) 156 | ) 157 | const index = indexArray().fromIso(iso).index(1) 158 | U.deepStrictEqual(index.getOption([]), none) 159 | U.deepStrictEqual(index.getOption(['1']), none) 160 | U.deepStrictEqual(index.getOption(['1', '2']), some(2)) 161 | 162 | U.deepStrictEqual(index.set(3)([]), []) 163 | U.deepStrictEqual(index.set(3)(['1']), ['1']) 164 | U.deepStrictEqual(index.set(3)(['1', '2']), ['1', '3']) 165 | }) 166 | }) 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build status](https://img.shields.io/travis/gcanti/monocle-ts/master.svg?style=flat-square)](https://travis-ci.org/gcanti/monocle-ts) 2 | [![dependency status](https://img.shields.io/david/gcanti/monocle-ts.svg?style=flat-square)](https://david-dm.org/gcanti/monocle-ts) 3 | ![npm downloads](https://img.shields.io/npm/dm/monocle-ts.svg) 4 | 5 | # Motivation 6 | 7 | (Adapted from [monocle site](https://www.optics.dev/Monocle/)) 8 | 9 | Modifying immutable nested object in JavaScript is verbose which makes code difficult to understand and reason about. 10 | 11 | Let's have a look at some examples: 12 | 13 | ```ts 14 | interface Street { 15 | num: number 16 | name: string 17 | } 18 | interface Address { 19 | city: string 20 | street: Street 21 | } 22 | interface Company { 23 | name: string 24 | address: Address 25 | } 26 | interface Employee { 27 | name: string 28 | company: Company 29 | } 30 | ``` 31 | 32 | Let’s say we have an employee and we need to upper case the first character of his company street name. Here is how we 33 | could write it in vanilla JavaScript 34 | 35 | ```ts 36 | const employee: Employee = { 37 | name: 'john', 38 | company: { 39 | name: 'awesome inc', 40 | address: { 41 | city: 'london', 42 | street: { 43 | num: 23, 44 | name: 'high street' 45 | } 46 | } 47 | } 48 | } 49 | 50 | const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1) 51 | 52 | const employeeCapitalized = { 53 | ...employee, 54 | company: { 55 | ...employee.company, 56 | address: { 57 | ...employee.company.address, 58 | street: { 59 | ...employee.company.address.street, 60 | name: capitalize(employee.company.address.street.name) 61 | } 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | As we can see copy is not convenient to update nested objects because we need to repeat ourselves. Let's see what could 68 | we do with `monocle-ts` 69 | 70 | ```ts 71 | import { Lens } from 'monocle-ts' 72 | 73 | const company = Lens.fromProp()('company') 74 | const address = Lens.fromProp()('address') 75 | const street = Lens.fromProp
()('street') 76 | const name = Lens.fromProp()('name') 77 | ``` 78 | 79 | `compose` takes two `Lenses`, one from `A` to `B` and another one from `B` to `C` and creates a third `Lens` from `A` to 80 | `C`. Therefore, after composing `company`, `address`, `street` and `name`, we obtain a `Lens` from `Employee` to 81 | `string` (the street name). Now we can use this `Lens` issued from the composition to modify the street name using the 82 | function `capitalize` 83 | 84 | ```ts 85 | const capitalizeName = company.compose(address).compose(street).compose(name).modify(capitalize) 86 | 87 | assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized) 88 | ``` 89 | 90 | You can use the `fromPath` API to avoid some boilerplate 91 | 92 | ```ts 93 | import { Lens } from 'monocle-ts' 94 | 95 | const name = Lens.fromPath()(['company', 'address', 'street', 'name']) 96 | 97 | const capitalizeName = name.modify(capitalize) 98 | 99 | assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized) // true 100 | ``` 101 | 102 | Here `modify` lift a function `string => string` to a function `Employee => Employee`. It works but it would be clearer 103 | if we could zoom into the first character of a `string` with a `Lens`. However, we cannot write such a `Lens` because 104 | `Lenses` require the field they are directed at to be _mandatory_. In our case the first character of a `string` is 105 | optional as a `string` can be empty. So we need another abstraction that would be a sort of partial Lens, in 106 | `monocle-ts` it is called an `Optional`. 107 | 108 | ```ts 109 | import { Optional } from 'monocle-ts' 110 | import { some, none } from 'fp-ts/Option' 111 | 112 | const firstLetterOptional = new Optional( 113 | (s) => (s.length > 0 ? some(s[0]) : none), 114 | (a) => (s) => (s.length > 0 ? a + s.substring(1) : s) 115 | ) 116 | 117 | const firstLetter = company.compose(address).compose(street).compose(name).asOptional().compose(firstLetterOptional) 118 | 119 | assert.deepStrictEqual(firstLetter.modify((s) => s.toUpperCase())(employee), employeeCapitalized) 120 | ``` 121 | 122 | Similarly to `compose` for lenses, `compose` for optionals takes two `Optionals`, one from `A` to `B` and another from 123 | `B` to `C` and creates a third `Optional` from `A` to `C`. All `Lenses` can be seen as `Optionals` where the optional 124 | element to zoom into is always present, hence composing an `Optional` and a `Lens` always produces an `Optional`. 125 | 126 | # TypeScript compatibility 127 | 128 | The stable version is tested against TypeScript 3.5.2, but should run with TypeScript 2.8.0+ too 129 | 130 | | `monocle-ts` version | required `typescript` version | 131 | | -------------------- | ----------------------------- | 132 | | 2.0.x+ | 3.5+ | 133 | | 1.x+ | 2.8.0+ | 134 | 135 | **Note**. If you are running `< typescript@3.0.1` you have to polyfill `unknown`. 136 | 137 | You can use [unknown-ts](https://github.com/gcanti/unknown-ts) as a polyfill. 138 | 139 | # Documentation 140 | 141 | - [API Reference](https://gcanti.github.io/monocle-ts/) 142 | 143 | ## Experimental modules (version `2.3+`) 144 | 145 | Experimental modules (\*) are published in order to get early feedback from the community. 146 | 147 | The experimental modules are **independent and backward-incompatible** with stable ones. 148 | 149 | (\*) A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 150 | 151 | From `monocle@2.3+` you can use the following experimental modules: 152 | 153 | - `Iso` 154 | - `Lens` 155 | - `Prism` 156 | - `Optional` 157 | - `Traversal` 158 | - `At` 159 | - `Ix` 160 | 161 | which implement the same features contained in `index.ts` but are `pipe`-based instead of `class`-based. 162 | 163 | Here's the same examples with the new API 164 | 165 | ```ts 166 | interface Street { 167 | num: number 168 | name: string 169 | } 170 | interface Address { 171 | city: string 172 | street: Street 173 | } 174 | interface Company { 175 | name: string 176 | address: Address 177 | } 178 | interface Employee { 179 | name: string 180 | company: Company 181 | } 182 | 183 | const employee: Employee = { 184 | name: 'john', 185 | company: { 186 | name: 'awesome inc', 187 | address: { 188 | city: 'london', 189 | street: { 190 | num: 23, 191 | name: 'high street' 192 | } 193 | } 194 | } 195 | } 196 | 197 | const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1) 198 | 199 | const employeeCapitalized = { 200 | ...employee, 201 | company: { 202 | ...employee.company, 203 | address: { 204 | ...employee.company.address, 205 | street: { 206 | ...employee.company.address.street, 207 | name: capitalize(employee.company.address.street.name) 208 | } 209 | } 210 | } 211 | } 212 | 213 | import * as assert from 'assert' 214 | import * as L from 'monocle-ts/Lens' 215 | import { pipe } from 'fp-ts/function' 216 | 217 | const capitalizeName = pipe( 218 | L.id(), 219 | L.prop('company'), 220 | L.prop('address'), 221 | L.prop('street'), 222 | L.prop('name'), 223 | L.modify(capitalize) 224 | ) 225 | 226 | assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized) 227 | 228 | import * as O from 'monocle-ts/Optional' 229 | import { some, none } from 'fp-ts/Option' 230 | 231 | const firstLetterOptional: O.Optional = { 232 | getOption: (s) => (s.length > 0 ? some(s[0]) : none), 233 | set: (a) => (s) => (s.length > 0 ? a + s.substring(1) : s) 234 | } 235 | 236 | const firstLetter = pipe( 237 | L.id(), 238 | L.prop('company'), 239 | L.prop('address'), 240 | L.prop('street'), 241 | L.prop('name'), 242 | L.composeOptional(firstLetterOptional) 243 | ) 244 | 245 | assert.deepStrictEqual( 246 | pipe( 247 | firstLetter, 248 | O.modify((s) => s.toUpperCase()) 249 | )(employee), 250 | employeeCapitalized 251 | ) 252 | ``` 253 | -------------------------------------------------------------------------------- /test/Lens.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as _ from '../src/Lens' 3 | import { pipe } from 'fp-ts/lib/pipeable' 4 | import * as O from 'fp-ts/lib/Option' 5 | import * as A from 'fp-ts/lib/ReadonlyArray' 6 | import * as T from '../src/Traversal' 7 | import * as Op from '../src/Optional' 8 | import * as Id from 'fp-ts/lib/Identity' 9 | import * as U from './util' 10 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 11 | import { ReadonlyNonEmptyArray } from 'fp-ts/lib/ReadonlyNonEmptyArray' 12 | 13 | describe('Lens', () => { 14 | describe('pipeables', () => { 15 | it('imap', () => { 16 | interface S { 17 | readonly a: number 18 | } 19 | const sa: _.Lens = pipe( 20 | _.id(), 21 | _.imap( 22 | (s) => s.a, 23 | (a) => ({ a }) 24 | ) 25 | ) 26 | U.deepStrictEqual(sa.get({ a: 1 }), 1) 27 | U.deepStrictEqual(sa.set(2)({ a: 1 }), { a: 2 }) 28 | }) 29 | }) 30 | 31 | it('compose', () => { 32 | interface S { 33 | readonly a: A 34 | } 35 | interface A { 36 | readonly b: number 37 | } 38 | const sa = pipe(_.id(), _.prop('a')) 39 | const ab = pipe(_.id(), _.prop('b')) 40 | const sb = _.Category.compose(ab, sa) 41 | U.deepStrictEqual(sb.get({ a: { b: 1 } }), 1) 42 | U.deepStrictEqual(sb.set(2)({ a: { b: 1 } }), { a: { b: 2 } }) 43 | }) 44 | 45 | it('composeTraversal', () => { 46 | interface S { 47 | readonly a: ReadonlyArray 48 | } 49 | const sa = pipe(_.id(), _.prop('a')) 50 | const ab = T.fromTraversable(A.readonlyArray)() 51 | const sb = pipe(sa, _.composeTraversal(ab)) 52 | U.deepStrictEqual(sb.modifyF(Id.identity)((n) => n * 2)({ a: [1, 2, 3] }), { a: [2, 4, 6] }) 53 | }) 54 | 55 | it('id', () => { 56 | interface S { 57 | readonly a: number 58 | } 59 | const ss = _.id() 60 | const s: S = { a: 1 } 61 | U.deepStrictEqual(ss.get(s), s) 62 | U.deepStrictEqual(ss.set(s)(s), s) 63 | }) 64 | 65 | it('fromNullable', () => { 66 | interface S { 67 | readonly a?: number 68 | } 69 | const sa = pipe(_.id(), _.prop('a'), _.fromNullable) 70 | const s: S = { a: 1 } 71 | U.deepStrictEqual(sa.getOption(s), O.some(1)) 72 | U.deepStrictEqual(sa.getOption({}), O.none) 73 | // should return the same reference 74 | assert.strictEqual(sa.set(1)(s), s) 75 | }) 76 | 77 | it('modify', () => { 78 | interface S { 79 | readonly a: number 80 | } 81 | const sa = pipe(_.id(), _.prop('a')) 82 | const f = pipe( 83 | sa, 84 | _.modify((a) => a * 2) 85 | ) 86 | U.deepStrictEqual(f({ a: 1 }), { a: 2 }) 87 | }) 88 | 89 | it('prop', () => { 90 | interface S { 91 | readonly a: { 92 | readonly b: number 93 | } 94 | } 95 | const sa = pipe(_.id(), _.prop('a'), _.prop('b')) 96 | const s: S = { a: { b: 1 } } 97 | assert.strictEqual(sa.get(s), 1) 98 | U.deepStrictEqual(sa.set(2)(s), { a: { b: 2 } }) 99 | // should return the same reference 100 | assert.strictEqual(sa.set(1)(s), s) 101 | }) 102 | 103 | it('props', () => { 104 | interface S { 105 | readonly a: { 106 | readonly b: string 107 | readonly c: number 108 | readonly d: boolean 109 | } 110 | } 111 | const sa = pipe(_.id(), _.prop('a'), _.props('b', 'c')) 112 | const s: S = { a: { b: 'b', c: 1, d: true } } 113 | U.deepStrictEqual(sa.get(s), { b: 'b', c: 1 }) 114 | U.deepStrictEqual(sa.set({ b: 'b', c: 2 })(s), { a: { b: 'b', c: 2, d: true } }) 115 | // should return the same reference 116 | assert.strictEqual(sa.set({ b: 'b', c: 1 })(s), s) 117 | }) 118 | 119 | it('component', () => { 120 | interface S { 121 | readonly a: readonly [string, number] 122 | } 123 | const sa = pipe(_.id(), _.prop('a'), _.component(1)) 124 | const s: S = { a: ['a', 1] } 125 | assert.strictEqual(sa.get(s), 1) 126 | U.deepStrictEqual(sa.set(2)(s), { a: ['a', 2] }) 127 | // should return the same reference 128 | assert.strictEqual(sa.set(1)(s), s) 129 | }) 130 | 131 | it('index', () => { 132 | interface S { 133 | readonly a: ReadonlyArray 134 | } 135 | const optional = pipe(_.id(), _.prop('a'), _.index(0)) 136 | U.deepStrictEqual(optional.getOption({ a: [] }), O.none) 137 | U.deepStrictEqual(optional.getOption({ a: [1] }), O.some(1)) 138 | U.deepStrictEqual(optional.set(2)({ a: [] }), { a: [] }) 139 | U.deepStrictEqual(optional.set(2)({ a: [1] }), { a: [2] }) 140 | // should return the same reference 141 | const empty: S = { a: [] } 142 | const full: S = { a: [1] } 143 | assert.strictEqual(optional.set(1)(empty), empty) 144 | assert.strictEqual(optional.set(1)(full), full) 145 | }) 146 | 147 | it('indexNonEmpty', () => { 148 | interface S { 149 | readonly a: ReadonlyNonEmptyArray 150 | } 151 | const optional = pipe(_.id(), _.prop('a'), _.indexNonEmpty(1)) 152 | U.deepStrictEqual(optional.getOption({ a: [1] }), O.none) 153 | U.deepStrictEqual(optional.getOption({ a: [1, 2] }), O.some(2)) 154 | U.deepStrictEqual(optional.set(3)({ a: [1] }), { a: [1] }) 155 | U.deepStrictEqual(optional.set(3)({ a: [1, 2] }), { a: [1, 3] }) 156 | // should return the same reference 157 | const full: S = { a: [1, 2] } 158 | assert.strictEqual(optional.set(2)(full), full) 159 | }) 160 | 161 | it('key', () => { 162 | interface S { 163 | readonly a: ReadonlyRecord 164 | } 165 | const sa = pipe(_.id(), _.prop('a'), _.key('k')) 166 | const empty: S = { a: {} } 167 | const full: S = { a: { k: 1, j: 2 } } 168 | U.deepStrictEqual(sa.getOption(empty), O.none) 169 | U.deepStrictEqual(sa.getOption(full), O.some(1)) 170 | U.deepStrictEqual(sa.set(2)(full), { a: { k: 2, j: 2 } }) 171 | // should return the same reference 172 | assert.strictEqual(sa.set(2)(empty), empty) 173 | assert.strictEqual(sa.set(1)(full), full) 174 | }) 175 | 176 | it('traverse', () => { 177 | type S = ReadonlyArray 178 | const sa = pipe(_.id(), _.traverse(A.readonlyArray)) 179 | const modify = pipe( 180 | sa, 181 | T.modify((s) => s.toUpperCase()) 182 | ) 183 | U.deepStrictEqual(modify(['a']), ['A']) 184 | }) 185 | 186 | it('compose', () => { 187 | interface S { 188 | readonly a: A 189 | } 190 | interface A { 191 | readonly b: number 192 | } 193 | const sa = pipe(_.id(), _.prop('a')) 194 | const ab = pipe(_.id(), _.prop('b')) 195 | const sb = pipe(sa, _.compose(ab)) 196 | U.deepStrictEqual(sb.get({ a: { b: 1 } }), 1) 197 | U.deepStrictEqual(sb.set(2)({ a: { b: 1 } }), { a: { b: 2 } }) 198 | }) 199 | 200 | it('composeOptional', () => { 201 | interface S { 202 | readonly a: string 203 | } 204 | const sa = pipe(_.id(), _.prop('a')) 205 | const ab: Op.Optional = Op.optional( 206 | (s) => (s.length > 0 ? O.some(s[0]) : O.none), 207 | (a) => (s) => (s.length > 0 ? a + s.substring(1) : s) 208 | ) 209 | const sb = pipe(sa, _.composeOptional(ab)) 210 | U.deepStrictEqual(sb.getOption({ a: '' }), O.none) 211 | U.deepStrictEqual(sb.getOption({ a: 'ab' }), O.some('a')) 212 | U.deepStrictEqual(sb.set('c')({ a: '' }), { a: '' }) 213 | U.deepStrictEqual(sb.set('c')({ a: 'ab' }), { a: 'cb' }) 214 | }) 215 | 216 | it('atKey', () => { 217 | type S = ReadonlyRecord 218 | const sa = pipe(_.id(), _.atKey('a')) 219 | U.deepStrictEqual(sa.get({ a: 1 }), O.some(1)) 220 | U.deepStrictEqual(sa.set(O.some(2))({ a: 1, b: 2 }), { a: 2, b: 2 }) 221 | U.deepStrictEqual(sa.set(O.some(1))({ b: 2 }), { a: 1, b: 2 }) 222 | U.deepStrictEqual(sa.set(O.none)({ a: 1, b: 2 }), { b: 2 }) 223 | }) 224 | 225 | it('filter', () => { 226 | interface S { 227 | readonly a: number 228 | } 229 | const sa = pipe( 230 | _.id(), 231 | _.prop('a'), 232 | _.filter((n) => n > 0) 233 | ) 234 | U.deepStrictEqual(sa.getOption({ a: 1 }), O.some(1)) 235 | U.deepStrictEqual(sa.getOption({ a: -1 }), O.none) 236 | U.deepStrictEqual(sa.set(2)({ a: 1 }), { a: 2 }) 237 | U.deepStrictEqual(sa.set(2)({ a: -1 }), { a: -1 }) 238 | }) 239 | 240 | it('findFirst', () => { 241 | type S = ReadonlyArray 242 | const optional = pipe( 243 | _.id(), 244 | _.findFirst((n) => n > 0) 245 | ) 246 | U.deepStrictEqual(optional.getOption([]), O.none) 247 | U.deepStrictEqual(optional.getOption([-1, -2, -3]), O.none) 248 | U.deepStrictEqual(optional.getOption([-1, 2, -3]), O.some(2)) 249 | U.deepStrictEqual(optional.set(3)([]), []) 250 | U.deepStrictEqual(optional.set(3)([-1, -2, -3]), [-1, -2, -3]) 251 | U.deepStrictEqual(optional.set(3)([-1, 2, -3]), [-1, 3, -3]) 252 | U.deepStrictEqual(optional.set(4)([-1, -2, 3]), [-1, -2, 4]) 253 | }) 254 | 255 | it('findFirstNonEmpty', () => { 256 | type S = ReadonlyNonEmptyArray 257 | const optional = pipe( 258 | _.id(), 259 | _.findFirstNonEmpty((n) => n > 0) 260 | ) 261 | U.deepStrictEqual(optional.getOption([-1, -2, -3]), O.none) 262 | U.deepStrictEqual(optional.getOption([-1, 2, -3]), O.some(2)) 263 | U.deepStrictEqual(optional.set(3)([-1, -2, -3]), [-1, -2, -3]) 264 | U.deepStrictEqual(optional.set(3)([-1, 2, -3]), [-1, 3, -3]) 265 | U.deepStrictEqual(optional.set(4)([-1, -2, 3]), [-1, -2, 4]) 266 | }) 267 | 268 | it('modifyF', () => { 269 | interface S { 270 | readonly a: number 271 | } 272 | const sa: _.Lens = pipe(_.id(), _.prop('a')) 273 | const f = pipe( 274 | sa, 275 | _.modifyF(O.option)((n) => (n > 0 ? O.some(n * 2) : O.none)) 276 | ) 277 | U.deepStrictEqual(f({ a: 1 }), O.some({ a: 2 })) 278 | U.deepStrictEqual(f({ a: -1 }), O.none) 279 | }) 280 | }) 281 | -------------------------------------------------------------------------------- /test/Optional.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as Id from 'fp-ts/lib/Identity' 3 | import * as O from 'fp-ts/lib/Option' 4 | import { pipe } from 'fp-ts/lib/pipeable' 5 | import * as A from 'fp-ts/lib/ReadonlyArray' 6 | import { ReadonlyNonEmptyArray } from 'fp-ts/lib/ReadonlyNonEmptyArray' 7 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 8 | import * as L from '../src/Lens' 9 | import * as _ from '../src/Optional' 10 | import * as P from '../src/Prism' 11 | import * as T from '../src/Traversal' 12 | import * as U from './util' 13 | 14 | type S = O.Option<{ 15 | readonly a: string 16 | readonly b: number 17 | readonly c: boolean 18 | }> 19 | 20 | describe('Optional', () => { 21 | describe('pipeables', () => { 22 | it('imap', () => { 23 | const sa = pipe( 24 | pipe(_.id(), _.some, _.prop('b')), 25 | _.imap( 26 | (n) => String(n), 27 | (s) => parseFloat(s) 28 | ) 29 | ) 30 | U.deepStrictEqual(sa.getOption(O.none), O.none) 31 | U.deepStrictEqual(sa.getOption(O.some({ a: 'a', b: 1, c: true })), O.some('1')) 32 | U.deepStrictEqual(sa.set('2')(O.some({ a: 'a', b: 1, c: true })), O.some({ a: 'a', b: 2, c: true })) 33 | }) 34 | }) 35 | 36 | it('compose', () => { 37 | type S = O.Option> 38 | const sa = pipe(_.id(), _.some) 39 | const ab = pipe(_.id>(), _.some) 40 | const sb = _.Category.compose(ab, sa) 41 | U.deepStrictEqual(sb.getOption(O.none), O.none) 42 | U.deepStrictEqual(sb.getOption(O.some(O.none)), O.none) 43 | U.deepStrictEqual(sb.getOption(O.some(O.some(1))), O.some(1)) 44 | }) 45 | 46 | it('composeLens', () => { 47 | type Inner = { readonly a: number } 48 | type S = O.Option 49 | const sa = pipe(_.id(), _.some) 50 | const ab = pipe(L.id(), L.prop('a')) 51 | const sb = pipe(sa, _.composeLens(ab)) 52 | U.deepStrictEqual(sb.getOption(O.none), O.none) 53 | U.deepStrictEqual(sb.getOption(O.some({ a: 1 })), O.some(1)) 54 | U.deepStrictEqual(sb.set(2)(O.some({ a: 1 })), O.some({ a: 2 })) 55 | U.deepStrictEqual(sb.set(2)(O.none), O.none) 56 | }) 57 | 58 | it('composePrism', () => { 59 | type S = O.Option> 60 | const sa = pipe(_.id(), _.some) 61 | const ab = pipe(P.id>(), P.some) 62 | const sb = pipe(sa, _.composePrism(ab)) 63 | U.deepStrictEqual(sb.getOption(O.none), O.none) 64 | U.deepStrictEqual(sb.getOption(O.some(O.none)), O.none) 65 | U.deepStrictEqual(sb.getOption(O.some(O.some(1))), O.some(1)) 66 | U.deepStrictEqual(sb.set(2)(O.none), O.none) 67 | U.deepStrictEqual(sb.set(2)(O.some(O.none)), O.some(O.none)) 68 | U.deepStrictEqual(sb.set(2)(O.some(O.some(1))), O.some(O.some(2))) 69 | }) 70 | 71 | it('composeTraversal', () => { 72 | type S = { 73 | readonly a: O.Option> 74 | } 75 | const sa = pipe(L.id(), L.prop('a'), L.some) 76 | const ab = T.fromTraversable(A.readonlyArray)() 77 | const sb = pipe(sa, _.composeTraversal(ab)) 78 | U.deepStrictEqual(sb.modifyF(Id.identity)((n) => n * 2)({ a: O.none }), { a: O.none }) 79 | U.deepStrictEqual(sb.modifyF(Id.identity)((n) => n * 2)({ a: O.some([1, 2, 3]) }), { a: O.some([2, 4, 6]) }) 80 | }) 81 | 82 | it('id', () => { 83 | const ss = _.id() 84 | U.deepStrictEqual(ss.getOption(O.none), O.some(O.none)) 85 | U.deepStrictEqual(ss.getOption(O.some({ a: 'a', b: 1, c: true })), O.some(O.some({ a: 'a', b: 1, c: true }))) 86 | }) 87 | 88 | it('prop', () => { 89 | const sa = pipe(_.id(), _.some, _.prop('a')) 90 | U.deepStrictEqual(sa.getOption(O.none), O.none) 91 | U.deepStrictEqual(sa.getOption(O.some({ a: 'a', b: 1, c: true })), O.some('a')) 92 | }) 93 | 94 | it('props', () => { 95 | const sa = pipe(_.id(), _.some, _.props('a', 'b')) 96 | U.deepStrictEqual(sa.getOption(O.none), O.none) 97 | U.deepStrictEqual(sa.getOption(O.some({ a: 'a', b: 1, c: true })), O.some({ a: 'a', b: 1 })) 98 | }) 99 | 100 | it('component', () => { 101 | type S = O.Option 102 | const sa = pipe(_.id(), _.some, _.component(1)) 103 | U.deepStrictEqual(sa.getOption(O.none), O.none) 104 | U.deepStrictEqual(sa.getOption(O.some(['a', 1])), O.some(1)) 105 | }) 106 | 107 | it('index', () => { 108 | type S = ReadonlyArray 109 | const optional = pipe(_.id(), _.index(0)) 110 | U.deepStrictEqual(optional.getOption([]), O.none) 111 | U.deepStrictEqual(optional.getOption([1]), O.some(1)) 112 | U.deepStrictEqual(optional.set(2)([]), []) 113 | U.deepStrictEqual(optional.set(2)([1]), [2]) 114 | // should return the same reference 115 | const empty: S = [] 116 | const full: S = [1] 117 | assert.strictEqual(optional.set(1)(empty), empty) 118 | assert.strictEqual(optional.set(1)(full), full) 119 | }) 120 | 121 | it('indexNonEmpty', () => { 122 | type S = ReadonlyNonEmptyArray 123 | const optional = pipe(_.id(), _.indexNonEmpty(1)) 124 | U.deepStrictEqual(optional.getOption([1]), O.none) 125 | U.deepStrictEqual(optional.getOption([1, 2]), O.some(2)) 126 | U.deepStrictEqual(optional.set(3)([1]), [1]) 127 | U.deepStrictEqual(optional.set(3)([1, 2]), [1, 3]) 128 | // should return the same reference 129 | const full: S = [1, 2] 130 | assert.strictEqual(optional.set(2)(full), full) 131 | }) 132 | 133 | it('key', () => { 134 | const sa = pipe(_.id>(), _.key('k')) 135 | U.deepStrictEqual(sa.getOption({ k: 1, j: 2 }), O.some(1)) 136 | }) 137 | 138 | it('atKey', () => { 139 | type S = ReadonlyRecord 140 | const sa = pipe(_.id(), _.atKey('a')) 141 | U.deepStrictEqual(sa.getOption({ a: 1 }), O.some(O.some(1))) 142 | U.deepStrictEqual(sa.set(O.some(2))({ a: 1, b: 2 }), { a: 2, b: 2 }) 143 | U.deepStrictEqual(sa.set(O.some(1))({ b: 2 }), { a: 1, b: 2 }) 144 | U.deepStrictEqual(sa.set(O.none)({ a: 1, b: 2 }), { b: 2 }) 145 | }) 146 | 147 | it('asTraversal', () => { 148 | const sa = pipe(_.id(), _.some, _.prop('b'), _.asTraversal) 149 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n - 1)(O.none), O.none) 150 | U.deepStrictEqual( 151 | sa.modifyF(Id.identity)((n) => n - 1)(O.some({ a: 'a', b: 2, c: true })), 152 | O.some({ 153 | a: 'a', 154 | b: 1, 155 | c: true 156 | }) 157 | ) 158 | }) 159 | 160 | it('filter', () => { 161 | type S = O.Option<{ readonly a: number }> 162 | const sa = pipe( 163 | _.id(), 164 | _.some, 165 | _.prop('a'), 166 | _.filter((n) => n > 0) 167 | ) 168 | U.deepStrictEqual(sa.getOption(O.some({ a: 1 })), O.some(1)) 169 | U.deepStrictEqual(sa.getOption(O.some({ a: -1 })), O.none) 170 | U.deepStrictEqual(sa.getOption(O.none), O.none) 171 | U.deepStrictEqual(sa.set(2)(O.none), O.none) 172 | U.deepStrictEqual(sa.set(2)(O.some({ a: 1 })), O.some({ a: 2 })) 173 | U.deepStrictEqual(sa.set(-1)(O.some({ a: 1 })), O.some({ a: -1 })) 174 | U.deepStrictEqual(sa.set(-1)(O.some({ a: -2 })), O.some({ a: -2 })) 175 | }) 176 | 177 | it('findFirst', () => { 178 | type S = O.Option<{ readonly a: ReadonlyArray }> 179 | const optional = pipe( 180 | _.id(), 181 | _.some, 182 | _.prop('a'), 183 | _.findFirst((n) => n > 0) 184 | ) 185 | U.deepStrictEqual(optional.getOption(O.none), O.none) 186 | U.deepStrictEqual(optional.getOption(O.some({ a: [] })), O.none) 187 | U.deepStrictEqual(optional.getOption(O.some({ a: [-1, -2, -3] })), O.none) 188 | U.deepStrictEqual(optional.getOption(O.some({ a: [-1, 2, -3] })), O.some(2)) 189 | U.deepStrictEqual(optional.set(3)(O.none), O.none) 190 | U.deepStrictEqual(optional.set(3)(O.some({ a: [] })), O.some({ a: [] })) 191 | U.deepStrictEqual(optional.set(3)(O.some({ a: [-1, -2, -3] })), O.some({ a: [-1, -2, -3] })) 192 | U.deepStrictEqual(optional.set(3)(O.some({ a: [-1, 2, -3] })), O.some({ a: [-1, 3, -3] })) 193 | U.deepStrictEqual(optional.set(4)(O.some({ a: [-1, -2, 3] })), O.some({ a: [-1, -2, 4] })) 194 | }) 195 | 196 | it('findFirstNonEmpty', () => { 197 | type S = O.Option<{ readonly a: ReadonlyNonEmptyArray }> 198 | const optional = pipe( 199 | _.id(), 200 | _.some, 201 | _.prop('a'), 202 | _.findFirstNonEmpty((n) => n > 0) 203 | ) 204 | U.deepStrictEqual(optional.getOption(O.none), O.none) 205 | U.deepStrictEqual(optional.getOption(O.some({ a: [-1, -2, -3] })), O.none) 206 | U.deepStrictEqual(optional.getOption(O.some({ a: [-1, 2, -3] })), O.some(2)) 207 | U.deepStrictEqual(optional.set(3)(O.none), O.none) 208 | U.deepStrictEqual(optional.set(3)(O.some({ a: [-1, -2, -3] })), O.some({ a: [-1, -2, -3] as const })) 209 | U.deepStrictEqual(optional.set(3)(O.some({ a: [-1, 2, -3] })), O.some({ a: [-1, 3, -3] as const })) 210 | U.deepStrictEqual(optional.set(4)(O.some({ a: [-1, -2, 3] })), O.some({ a: [-1, -2, 4] as const })) 211 | }) 212 | 213 | it('traverse', () => { 214 | type S = O.Option<{ readonly a: ReadonlyArray }> 215 | const sa = pipe(_.id(), _.some, _.prop('a'), _.traverse(A.readonlyArray)) 216 | const modify = pipe( 217 | sa, 218 | T.modify((s) => s.toUpperCase()) 219 | ) 220 | U.deepStrictEqual(modify(O.some({ a: ['a'] })), O.some({ a: ['A'] })) 221 | }) 222 | 223 | it('fromNullable', () => { 224 | interface S { 225 | readonly a?: number 226 | } 227 | const sa = pipe(_.id(), _.prop('a'), _.fromNullable) 228 | U.deepStrictEqual(sa.getOption({}), O.none) 229 | U.deepStrictEqual(sa.getOption({ a: undefined }), O.none) 230 | U.deepStrictEqual(sa.getOption({ a: 1 }), O.some(1)) 231 | U.deepStrictEqual(sa.set(2)({}), {}) 232 | U.deepStrictEqual(sa.set(2)({ a: undefined }), { a: undefined }) 233 | U.deepStrictEqual(sa.set(2)({ a: 1 }), { a: 2 }) 234 | }) 235 | 236 | it('modifyF', () => { 237 | const sa = pipe(_.id>(), _.index(0)) 238 | const f = pipe( 239 | sa, 240 | _.modifyF(O.option)((n) => (n > 0 ? O.some(n * 2) : O.none)) 241 | ) 242 | U.deepStrictEqual(f([]), O.some([])) 243 | U.deepStrictEqual(f([1, 2, 3]), O.some([2, 2, 3])) 244 | U.deepStrictEqual(f([-1, 2, 3]), O.none) 245 | }) 246 | 247 | it('setOption', () => { 248 | const sa = pipe(_.id>(), _.index(0)) 249 | U.deepStrictEqual(pipe(sa, _.setOption(2))([]), O.none) 250 | U.deepStrictEqual(pipe(sa, _.setOption(2))([1, 3]), O.some([2, 3])) 251 | }) 252 | }) 253 | -------------------------------------------------------------------------------- /test/Iso.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as Id from 'fp-ts/lib/Identity' 3 | import * as O from 'fp-ts/lib/Option' 4 | import { pipe } from 'fp-ts/lib/pipeable' 5 | import * as RA from 'fp-ts/lib/ReadonlyArray' 6 | import { ReadonlyNonEmptyArray } from 'fp-ts/lib/ReadonlyNonEmptyArray' 7 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 8 | import * as _ from '../src/Iso' 9 | import * as L from '../src/Lens' 10 | import * as Op from '../src/Optional' 11 | import * as P from '../src/Prism' 12 | import * as T from '../src/Traversal' 13 | import * as U from './util' 14 | 15 | const numberFromString: _.Iso = _.iso(String, parseFloat) 16 | 17 | const double: _.Iso = _.iso( 18 | (n) => n * 2, 19 | (n) => n / 2 20 | ) 21 | 22 | describe('Iso', () => { 23 | describe('pipeables', () => { 24 | it('imap', () => { 25 | const sb = pipe( 26 | numberFromString, 27 | _.imap( 28 | (s) => '+' + s, 29 | (s) => s.substring(1) 30 | ) 31 | ) 32 | U.deepStrictEqual(sb.get(1), '+1') 33 | U.deepStrictEqual(sb.reverseGet('+1'), 1) 34 | }) 35 | }) 36 | 37 | describe('instances', () => { 38 | it('compose', () => { 39 | const sb = _.Category.compose(numberFromString, double) 40 | U.deepStrictEqual(sb.get(1), '2') 41 | U.deepStrictEqual(sb.reverseGet('2'), 1) 42 | }) 43 | }) 44 | 45 | it('id', () => { 46 | const ss = _.id() 47 | U.deepStrictEqual(ss.get(1), 1) 48 | U.deepStrictEqual(ss.reverseGet(1), 1) 49 | }) 50 | 51 | it('reverse', () => { 52 | const as = _.reverse(numberFromString) 53 | U.deepStrictEqual(as.get('1'), 1) 54 | U.deepStrictEqual(as.reverseGet(1), '1') 55 | }) 56 | 57 | it('compose', () => { 58 | const sa = double 59 | const ab = numberFromString 60 | const sb = pipe(sa, _.compose(ab)) 61 | U.deepStrictEqual(sb.get(1), '2') 62 | U.deepStrictEqual(sb.reverseGet('2'), 1) 63 | }) 64 | 65 | it('composeLens', () => { 66 | type S = { readonly a: number } 67 | const sa = _.id() 68 | const ab = pipe(L.id(), L.prop('a')) 69 | const sb = pipe(sa, _.composeLens(ab)) 70 | U.deepStrictEqual(sb.get({ a: 1 }), 1) 71 | U.deepStrictEqual(sb.set(2)({ a: 1 }), { a: 2 }) 72 | }) 73 | 74 | it('composePrism', () => { 75 | type S = O.Option 76 | const sa = _.id() 77 | const ab = pipe(P.id(), P.some) 78 | const sb = pipe(sa, _.composePrism(ab)) 79 | U.deepStrictEqual(sb.getOption(O.none), O.none) 80 | U.deepStrictEqual(sb.getOption(O.some(1)), O.some(1)) 81 | U.deepStrictEqual(sb.reverseGet(1), O.some(1)) 82 | }) 83 | 84 | it('composeOptional', () => { 85 | type S = { readonly a: O.Option } 86 | const sa = _.id() 87 | const ab = pipe(Op.id(), Op.prop('a'), Op.some) 88 | const sb = pipe(sa, _.composeOptional(ab)) 89 | U.deepStrictEqual(sb.getOption({ a: O.none }), O.none) 90 | U.deepStrictEqual(sb.getOption({ a: O.some(1) }), O.some(1)) 91 | U.deepStrictEqual(sb.set(2)({ a: O.none }), { a: O.none }) 92 | U.deepStrictEqual(sb.set(2)({ a: O.some(1) }), { a: O.some(2) }) 93 | }) 94 | 95 | it('composeTraversal', () => { 96 | type S = ReadonlyArray 97 | const sa = _.id() 98 | const ab = pipe(T.id(), T.traverse(RA.readonlyArray)) 99 | const sb = pipe(sa, _.composeTraversal(ab)) 100 | U.deepStrictEqual(sb.modifyF(Id.identity)((n) => n * 2)([1, 2, 3]), [2, 4, 6]) 101 | }) 102 | 103 | it('asTraversal', () => { 104 | const sa = pipe(double, _.asTraversal) 105 | U.deepStrictEqual(sa.modifyF(Id.identity)((n) => n - 1)(3), 2.5) 106 | }) 107 | 108 | it('modifyF', () => { 109 | const f = pipe( 110 | double, 111 | _.modifyF(O.option)((n) => (n > 0 ? O.some(n * 2) : O.none)) 112 | ) 113 | U.deepStrictEqual(f(1), O.some(2)) 114 | U.deepStrictEqual(f(-1), O.none) 115 | }) 116 | 117 | it('fromNullable', () => { 118 | type S = { readonly a: number } | null 119 | type A = readonly [number] | null 120 | const sa: _.Iso = _.iso( 121 | (s) => (s === null ? null : ([s.a] as const)), 122 | (a) => (a === null ? null : { a: a[0] }) 123 | ) 124 | const prism = _.fromNullable(sa) 125 | U.deepStrictEqual(prism.getOption(null), O.none) 126 | U.deepStrictEqual(prism.getOption({ a: 1 }), O.some([1] as const)) 127 | U.deepStrictEqual(prism.reverseGet([1]), { a: 1 }) 128 | }) 129 | 130 | it('filter', () => { 131 | type S = { readonly a: number } 132 | type A = readonly [number] 133 | const sa: _.Iso = _.iso( 134 | (s) => [s.a] as const, 135 | (a) => ({ a: a[0] }) 136 | ) 137 | const prism = pipe( 138 | sa, 139 | _.filter((a) => a[0] > 0) 140 | ) 141 | U.deepStrictEqual(prism.getOption({ a: -1 }), O.none) 142 | U.deepStrictEqual(prism.getOption({ a: 1 }), O.some([1] as const)) 143 | U.deepStrictEqual(prism.reverseGet([1]), { a: 1 }) 144 | U.deepStrictEqual(prism.reverseGet([-1]), { a: -1 }) 145 | }) 146 | 147 | it('prop', () => { 148 | type S = readonly [number] 149 | type A = { readonly a: number } 150 | const sa: _.Iso = _.iso( 151 | (s) => ({ a: s[0] }), 152 | (a) => [a.a] 153 | ) 154 | const lens = pipe(sa, _.prop('a')) 155 | U.deepStrictEqual(lens.get([1]), 1) 156 | U.deepStrictEqual(lens.set(2)([1]), [2]) 157 | }) 158 | 159 | it('props', () => { 160 | type S = readonly [number, string] 161 | type A = { readonly a: number; readonly b: string } 162 | const sa: _.Iso = _.iso( 163 | (s) => ({ a: s[0], b: s[1] }), 164 | (a) => [a.a, a.b] 165 | ) 166 | const lens = pipe(sa, _.props('a', 'b')) 167 | U.deepStrictEqual(lens.get([1, 'b']), { a: 1, b: 'b' }) 168 | U.deepStrictEqual(lens.set({ a: 2, b: 'c' })([1, 'b']), [2, 'c']) 169 | }) 170 | 171 | it('component', () => { 172 | type S = { readonly a: number; readonly b: string } 173 | type A = readonly [number, string] 174 | const sa: _.Iso = _.iso( 175 | (s) => [s.a, s.b] as const, 176 | (a) => ({ a: a[0], b: a[1] }) 177 | ) 178 | const lens = pipe(sa, _.component(1)) 179 | U.deepStrictEqual(lens.get({ a: 1, b: 'b' }), 'b') 180 | U.deepStrictEqual(lens.set('c')({ a: 1, b: 'b' }), { a: 1, b: 'c' }) 181 | }) 182 | 183 | it('index', () => { 184 | type S = { readonly a: ReadonlyArray } 185 | type A = ReadonlyArray 186 | const sa: _.Iso = _.iso( 187 | (s) => s.a, 188 | (a) => ({ a }) 189 | ) 190 | const optional = pipe(sa, _.index(0)) 191 | U.deepStrictEqual(optional.getOption({ a: [] }), O.none) 192 | U.deepStrictEqual(optional.getOption({ a: [1] }), O.some(1)) 193 | U.deepStrictEqual(optional.set(2)({ a: [] }), { a: [] }) 194 | U.deepStrictEqual(optional.set(2)({ a: [1] }), { a: [2] }) 195 | // should return the same reference 196 | const empty: S = { a: [] } 197 | const full: S = { a: [1] } 198 | assert.strictEqual(optional.set(1)(empty), empty) 199 | assert.strictEqual(optional.set(1)(full), full) 200 | }) 201 | 202 | it('indexNonEmpty', () => { 203 | type S = { readonly a: ReadonlyNonEmptyArray } 204 | type A = ReadonlyNonEmptyArray 205 | const sa: _.Iso = _.iso( 206 | (s) => s.a, 207 | (a) => ({ a }) 208 | ) 209 | const optional = pipe(sa, _.indexNonEmpty(1)) 210 | U.deepStrictEqual(optional.getOption({ a: [1] }), O.none) 211 | U.deepStrictEqual(optional.getOption({ a: [1, 2] }), O.some(2)) 212 | U.deepStrictEqual(optional.set(3)({ a: [1] }), { a: [1] }) 213 | U.deepStrictEqual(optional.set(3)({ a: [1, 2] }), { a: [1, 3] }) 214 | // should return the same reference 215 | const s: S = { a: [1, 2] } 216 | assert.strictEqual(optional.set(2)(s), s) 217 | }) 218 | 219 | it('key', () => { 220 | type S = { readonly a: ReadonlyRecord } 221 | type A = ReadonlyRecord 222 | const sa: _.Iso = _.iso( 223 | (s) => s.a, 224 | (a) => ({ a }) 225 | ) 226 | const optional = pipe(sa, _.key('b')) 227 | U.deepStrictEqual(optional.getOption({ a: {} }), O.none) 228 | U.deepStrictEqual(optional.getOption({ a: { b: 1 } }), O.some(1)) 229 | U.deepStrictEqual(optional.set(2)({ a: {} }), { a: {} }) 230 | U.deepStrictEqual(optional.set(2)({ a: { b: 1 } }), { a: { b: 2 } }) 231 | }) 232 | 233 | it('atKey', () => { 234 | type S = { readonly a: ReadonlyRecord } 235 | type A = ReadonlyRecord 236 | const sa: _.Iso = _.iso( 237 | (s) => s.a, 238 | (a) => ({ a }) 239 | ) 240 | const optional = pipe(sa, _.atKey('b')) 241 | U.deepStrictEqual(optional.get({ a: {} }), O.none) 242 | U.deepStrictEqual(optional.get({ a: { b: 1 } }), O.some(1)) 243 | U.deepStrictEqual(optional.set(O.none)({ a: {} }), { a: {} }) 244 | U.deepStrictEqual(optional.set(O.some(1))({ a: {} }), { a: { b: 1 } }) 245 | U.deepStrictEqual(optional.set(O.none)({ a: { b: 1 } }), { a: {} }) 246 | U.deepStrictEqual(optional.set(O.some(2))({ a: { b: 1 } }), { a: { b: 2 } }) 247 | }) 248 | 249 | it('traverse', () => { 250 | type S = { readonly a: ReadonlyArray } 251 | type A = ReadonlyArray 252 | const sa: _.Iso = _.iso( 253 | (s) => s.a, 254 | (a) => ({ a }) 255 | ) 256 | const traversal = pipe(sa, _.traverse(RA.readonlyArray)) 257 | U.deepStrictEqual(traversal.modifyF(Id.identity)((n) => n * 2)({ a: [1, 2, 3] }), { a: [2, 4, 6] }) 258 | }) 259 | 260 | it('findFirst', () => { 261 | type S = { readonly a: ReadonlyArray } 262 | type A = ReadonlyArray 263 | const sa: _.Iso = _.iso( 264 | (s) => s.a, 265 | (a) => ({ a }) 266 | ) 267 | const optional = pipe( 268 | sa, 269 | _.findFirst((n) => n > 0) 270 | ) 271 | U.deepStrictEqual(optional.getOption({ a: [] }), O.none) 272 | U.deepStrictEqual(optional.getOption({ a: [-1] }), O.none) 273 | U.deepStrictEqual(optional.getOption({ a: [-1, 2] }), O.some(2)) 274 | U.deepStrictEqual(optional.set(1)({ a: [] }), { a: [] }) 275 | U.deepStrictEqual(optional.set(1)({ a: [-1] }), { a: [-1] }) 276 | U.deepStrictEqual(optional.set(3)({ a: [-1, 2] }), { a: [-1, 3] }) 277 | }) 278 | 279 | it('findFirstNonEmpty', () => { 280 | type S = { readonly a: ReadonlyNonEmptyArray } 281 | type A = ReadonlyNonEmptyArray 282 | const sa: _.Iso = _.iso( 283 | (s) => s.a, 284 | (a) => ({ a }) 285 | ) 286 | const optional = pipe( 287 | sa, 288 | _.findFirstNonEmpty((n) => n > 0) 289 | ) 290 | U.deepStrictEqual(optional.getOption({ a: [-1] }), O.none) 291 | U.deepStrictEqual(optional.getOption({ a: [-1, 2] }), O.some(2)) 292 | U.deepStrictEqual(optional.set(1)({ a: [-1] }), { a: [-1] }) 293 | U.deepStrictEqual(optional.set(3)({ a: [-1, 2] }), { a: [-1, 3] }) 294 | }) 295 | }) 296 | -------------------------------------------------------------------------------- /docs/modules/Lens.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Lens.ts 3 | nav_order: 17 4 | parent: Modules 5 | --- 6 | 7 | ## Lens overview 8 | 9 | **This module is experimental** 10 | 11 | Experimental features are published in order to get early feedback from the community. 12 | 13 | A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 14 | 15 | A `Lens` is an optic used to zoom inside a product. 16 | 17 | `Lens`es have two type parameters generally called `S` and `A`: `Lens` where `S` represents the product and `A` 18 | an element inside of `S`. 19 | 20 | Laws: 21 | 22 | 1. `get(set(a)(s)) = a` 23 | 2. `set(get(s))(s) = s` 24 | 3. `set(a)(set(a)(s)) = set(a)(s)` 25 | 26 | Added in v2.3.0 27 | 28 | --- 29 | 30 |

Table of contents

31 | 32 | - [Invariant](#invariant) 33 | - [imap](#imap) 34 | - [combinators](#combinators) 35 | - [atKey](#atkey) 36 | - [component](#component) 37 | - [filter](#filter) 38 | - [findFirst](#findfirst) 39 | - [findFirstNonEmpty](#findfirstnonempty) 40 | - [fromNullable](#fromnullable) 41 | - [index](#index) 42 | - [indexNonEmpty](#indexnonempty) 43 | - [key](#key) 44 | - [left](#left) 45 | - [modify](#modify) 46 | - [modifyF](#modifyf) 47 | - [prop](#prop) 48 | - [props](#props) 49 | - [right](#right) 50 | - [some](#some) 51 | - [traverse](#traverse) 52 | - [compositions](#compositions) 53 | - [compose](#compose) 54 | - [composeIso](#composeiso) 55 | - [composeLens](#composelens) 56 | - [composeOptional](#composeoptional) 57 | - [composePrism](#composeprism) 58 | - [composeTraversal](#composetraversal) 59 | - [constructors](#constructors) 60 | - [id](#id) 61 | - [lens](#lens) 62 | - [converters](#converters) 63 | - [asOptional](#asoptional) 64 | - [asTraversal](#astraversal) 65 | - [instances](#instances) 66 | - [Category](#category) 67 | - [Invariant](#invariant-1) 68 | - [Semigroupoid](#semigroupoid) 69 | - [URI](#uri) 70 | - [URI (type alias)](#uri-type-alias) 71 | - [model](#model) 72 | - [Lens (interface)](#lens-interface) 73 | 74 | --- 75 | 76 | # Invariant 77 | 78 | ## imap 79 | 80 | **Signature** 81 | 82 | ```ts 83 | export declare const imap: (f: (a: A) => B, g: (b: B) => A) => (sa: Lens) => Lens 84 | ``` 85 | 86 | Added in v2.3.0 87 | 88 | # combinators 89 | 90 | ## atKey 91 | 92 | Return a `Lens` from a `Lens` focused on a required key of a `ReadonlyRecord`. 93 | 94 | **Signature** 95 | 96 | ```ts 97 | export declare const atKey: (key: string) => (sa: Lens>>) => Lens> 98 | ``` 99 | 100 | Added in v2.3.0 101 | 102 | ## component 103 | 104 | Return a `Lens` from a `Lens` focused on a component of a tuple. 105 | 106 | **Signature** 107 | 108 | ```ts 109 | export declare const component:
( 110 | prop: P 111 | ) => (sa: Lens) => Lens 112 | ``` 113 | 114 | Added in v2.3.0 115 | 116 | ## filter 117 | 118 | **Signature** 119 | 120 | ```ts 121 | export declare function filter(refinement: Refinement): (sa: Lens) => Optional 122 | export declare function filter(predicate: Predicate): (sa: Lens) => Optional 123 | ``` 124 | 125 | Added in v2.3.0 126 | 127 | ## findFirst 128 | 129 | **Signature** 130 | 131 | ```ts 132 | export declare function findFirst( 133 | refinement: Refinement 134 | ): (sa: Lens>) => Optional 135 | export declare function findFirst(predicate: Predicate): (sa: Lens>) => Optional 136 | ``` 137 | 138 | Added in v2.3.2 139 | 140 | ## findFirstNonEmpty 141 | 142 | **Signature** 143 | 144 | ```ts 145 | export declare function findFirstNonEmpty( 146 | refinement: Refinement 147 | ): (sa: Lens>) => Optional 148 | export declare function findFirstNonEmpty( 149 | predicate: Predicate 150 | ): (sa: Lens>) => Optional 151 | ``` 152 | 153 | Added in v2.3.8 154 | 155 | ## fromNullable 156 | 157 | Return a `Optional` from a `Lens` focused on a nullable value. 158 | 159 | **Signature** 160 | 161 | ```ts 162 | export declare const fromNullable: (sa: Lens) => Optional> 163 | ``` 164 | 165 | Added in v2.3.0 166 | 167 | ## index 168 | 169 | Return a `Optional` from a `Lens` focused on an index of a `ReadonlyArray`. 170 | 171 | **Signature** 172 | 173 | ```ts 174 | export declare const index: (i: number) => (sa: Lens) => Optional 175 | ``` 176 | 177 | Added in v2.3.0 178 | 179 | ## indexNonEmpty 180 | 181 | Return a `Optional` from a `Lens` focused on an index of a `ReadonlyNonEmptyArray`. 182 | 183 | **Signature** 184 | 185 | ```ts 186 | export declare const indexNonEmpty: (i: number) => (sa: Lens>) => Optional 187 | ``` 188 | 189 | Added in v2.3.8 190 | 191 | ## key 192 | 193 | Return a `Optional` from a `Lens` focused on a key of a `ReadonlyRecord`. 194 | 195 | **Signature** 196 | 197 | ```ts 198 | export declare const key: (key: string) => (sa: Lens>>) => Optional 199 | ``` 200 | 201 | Added in v2.3.0 202 | 203 | ## left 204 | 205 | Return a `Optional` from a `Lens` focused on the `Left` of a `Either` type. 206 | 207 | **Signature** 208 | 209 | ```ts 210 | export declare const left: (sea: Lens>) => Optional 211 | ``` 212 | 213 | Added in v2.3.0 214 | 215 | ## modify 216 | 217 | **Signature** 218 | 219 | ```ts 220 | export declare const modify: (f: (a: A) => B) => (sa: Lens) => (s: S) => S 221 | ``` 222 | 223 | Added in v2.3.0 224 | 225 | ## modifyF 226 | 227 | **Signature** 228 | 229 | ```ts 230 | export declare function modifyF( 231 | F: Functor3 232 | ): (f: (a: A) => Kind3) => (sa: Lens) => (s: S) => Kind3 233 | export declare function modifyF( 234 | F: Functor2 235 | ): (f: (a: A) => Kind2) => (sa: Lens) => (s: S) => Kind2 236 | export declare function modifyF( 237 | F: Functor1 238 | ): (f: (a: A) => Kind) => (sa: Lens) => (s: S) => Kind 239 | export declare function modifyF( 240 | F: Functor 241 | ): (f: (a: A) => HKT) => (sa: Lens) => (s: S) => HKT 242 | ``` 243 | 244 | Added in v2.3.5 245 | 246 | ## prop 247 | 248 | Return a `Lens` from a `Lens` and a prop. 249 | 250 | **Signature** 251 | 252 | ```ts 253 | export declare const prop: (prop: P) => (sa: Lens) => Lens 254 | ``` 255 | 256 | Added in v2.3.0 257 | 258 | ## props 259 | 260 | Return a `Lens` from a `Lens` and a list of props. 261 | 262 | **Signature** 263 | 264 | ```ts 265 | export declare const props: ( 266 | props_0: P, 267 | props_1: P, 268 | ...props_2: P[] 269 | ) => (sa: Lens) => Lens 270 | ``` 271 | 272 | Added in v2.3.0 273 | 274 | ## right 275 | 276 | Return a `Optional` from a `Lens` focused on the `Right` of a `Either` type. 277 | 278 | **Signature** 279 | 280 | ```ts 281 | export declare const right: (sea: Lens>) => Optional 282 | ``` 283 | 284 | Added in v2.3.0 285 | 286 | ## some 287 | 288 | Return a `Optional` from a `Lens` focused on the `Some` of a `Option` type. 289 | 290 | **Signature** 291 | 292 | ```ts 293 | export declare const some: (soa: Lens>) => Optional 294 | ``` 295 | 296 | Added in v2.3.0 297 | 298 | ## traverse 299 | 300 | Return a `Traversal` from a `Lens` focused on a `Traversable`. 301 | 302 | **Signature** 303 | 304 | ```ts 305 | export declare function traverse( 306 | T: Traversable1 307 | ): (sta: Lens>) => Traversal 308 | ``` 309 | 310 | Added in v2.3.0 311 | 312 | # compositions 313 | 314 | ## compose 315 | 316 | Compose a `Lens` with a `Lens`. 317 | 318 | **Signature** 319 | 320 | ```ts 321 | export declare const compose: (ab: Lens) => (sa: Lens) => Lens 322 | ``` 323 | 324 | Added in v2.3.0 325 | 326 | ## composeIso 327 | 328 | Compose a `Lens` with a `Iso`. 329 | 330 | **Signature** 331 | 332 | ```ts 333 | export declare const composeIso: (ab: Iso) => (sa: Lens) => Lens 334 | ``` 335 | 336 | Added in v2.3.8 337 | 338 | ## composeLens 339 | 340 | Alias of `compose`. 341 | 342 | **Signature** 343 | 344 | ```ts 345 | export declare const composeLens: (ab: Lens) => (sa: Lens) => Lens 346 | ``` 347 | 348 | Added in v2.3.8 349 | 350 | ## composeOptional 351 | 352 | Compose a `Lens` with an `Optional`. 353 | 354 | **Signature** 355 | 356 | ```ts 357 | export declare const composeOptional: (ab: Optional) => (sa: Lens) => Optional 358 | ``` 359 | 360 | Added in v2.3.0 361 | 362 | ## composePrism 363 | 364 | Compose a `Lens` with a `Prism`. 365 | 366 | **Signature** 367 | 368 | ```ts 369 | export declare const composePrism: (ab: Prism) => (sa: Lens) => Optional 370 | ``` 371 | 372 | Added in v2.3.0 373 | 374 | ## composeTraversal 375 | 376 | Compose a `Lens` with an `Traversal`. 377 | 378 | **Signature** 379 | 380 | ```ts 381 | export declare const composeTraversal: (ab: Traversal) => (sa: Lens) => Traversal 382 | ``` 383 | 384 | Added in v2.3.8 385 | 386 | # constructors 387 | 388 | ## id 389 | 390 | **Signature** 391 | 392 | ```ts 393 | export declare const id: () => Lens 394 | ``` 395 | 396 | Added in v2.3.0 397 | 398 | ## lens 399 | 400 | **Signature** 401 | 402 | ```ts 403 | export declare const lens: (get: (s: S) => A, set: (a: A) => (s: S) => S) => Lens 404 | ``` 405 | 406 | Added in v2.3.8 407 | 408 | # converters 409 | 410 | ## asOptional 411 | 412 | View a `Lens` as a `Optional`. 413 | 414 | **Signature** 415 | 416 | ```ts 417 | export declare const asOptional: (sa: Lens) => Optional 418 | ``` 419 | 420 | Added in v2.3.0 421 | 422 | ## asTraversal 423 | 424 | View a `Lens` as a `Traversal`. 425 | 426 | **Signature** 427 | 428 | ```ts 429 | export declare const asTraversal: (sa: Lens) => Traversal 430 | ``` 431 | 432 | Added in v2.3.0 433 | 434 | # instances 435 | 436 | ## Category 437 | 438 | **Signature** 439 | 440 | ```ts 441 | export declare const Category: Category2<'monocle-ts/Lens'> 442 | ``` 443 | 444 | Added in v2.3.0 445 | 446 | ## Invariant 447 | 448 | **Signature** 449 | 450 | ```ts 451 | export declare const Invariant: Invariant2<'monocle-ts/Lens'> 452 | ``` 453 | 454 | Added in v2.3.0 455 | 456 | ## Semigroupoid 457 | 458 | **Signature** 459 | 460 | ```ts 461 | export declare const Semigroupoid: Semigroupoid2<'monocle-ts/Lens'> 462 | ``` 463 | 464 | Added in v2.3.8 465 | 466 | ## URI 467 | 468 | **Signature** 469 | 470 | ```ts 471 | export declare const URI: 'monocle-ts/Lens' 472 | ``` 473 | 474 | Added in v2.3.0 475 | 476 | ## URI (type alias) 477 | 478 | **Signature** 479 | 480 | ```ts 481 | export type URI = typeof URI 482 | ``` 483 | 484 | Added in v2.3.0 485 | 486 | # model 487 | 488 | ## Lens (interface) 489 | 490 | **Signature** 491 | 492 | ```ts 493 | export interface Lens { 494 | readonly get: (s: S) => A 495 | readonly set: (a: A) => (s: S) => S 496 | } 497 | ``` 498 | 499 | Added in v2.3.0 500 | -------------------------------------------------------------------------------- /test/Prism.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as E from 'fp-ts/lib/Either' 3 | import * as Id from 'fp-ts/lib/Identity' 4 | import * as O from 'fp-ts/lib/Option' 5 | import { pipe } from 'fp-ts/lib/pipeable' 6 | import * as A from 'fp-ts/lib/ReadonlyArray' 7 | import { ReadonlyNonEmptyArray } from 'fp-ts/lib/ReadonlyNonEmptyArray' 8 | import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord' 9 | import * as Op from '../src/Optional' 10 | import * as _ from '../src/Prism' 11 | import * as T from '../src/Traversal' 12 | import * as U from './util' 13 | 14 | // ------------------------------------------------------------------------------------- 15 | // model 16 | // ------------------------------------------------------------------------------------- 17 | 18 | type Leaf = { 19 | readonly _tag: 'Leaf' 20 | } 21 | type Node = { 22 | readonly _tag: 'Node' 23 | readonly value: number 24 | readonly left: Tree 25 | readonly right: Tree 26 | } 27 | type Tree = Leaf | Node 28 | 29 | // ------------------------------------------------------------------------------------- 30 | // constructors 31 | // ------------------------------------------------------------------------------------- 32 | 33 | const leaf: Tree = { _tag: 'Leaf' } 34 | const node = (value: number, left: Tree, right: Tree): Tree => ({ _tag: 'Node', value, left, right }) 35 | 36 | // ------------------------------------------------------------------------------------- 37 | // primitives 38 | // ------------------------------------------------------------------------------------- 39 | 40 | const value: _.Prism = { 41 | getOption: (s) => (s._tag === 'Node' ? O.some(s.value) : O.none), 42 | reverseGet: (a) => node(a, leaf, leaf) 43 | } 44 | 45 | describe('Prism', () => { 46 | describe('pipeables', () => { 47 | it('imap', () => { 48 | const sa = pipe( 49 | value, 50 | _.imap( 51 | (n) => String(n), 52 | (s) => parseFloat(s) 53 | ) 54 | ) 55 | U.deepStrictEqual(sa.getOption(leaf), O.none) 56 | U.deepStrictEqual(sa.getOption(node(1, leaf, leaf)), O.some('1')) 57 | U.deepStrictEqual(sa.reverseGet('1'), node(1, leaf, leaf)) 58 | }) 59 | }) 60 | 61 | describe('instances', () => { 62 | it('compose', () => { 63 | type S = O.Option 64 | const sa = pipe(_.id(), _.some) 65 | const ab = value 66 | const sb = _.Category.compose(ab, sa) 67 | U.deepStrictEqual(sb.getOption(O.none), O.none) 68 | U.deepStrictEqual(sb.getOption(O.some(leaf)), O.none) 69 | U.deepStrictEqual(sb.getOption(O.some(node(1, leaf, leaf))), O.some(1)) 70 | U.deepStrictEqual(sb.reverseGet(1), O.some(node(1, leaf, leaf))) 71 | }) 72 | }) 73 | 74 | it('id', () => { 75 | const ss = _.id() 76 | U.deepStrictEqual(ss.getOption(leaf), O.some(leaf)) 77 | U.deepStrictEqual(ss.reverseGet(leaf), leaf) 78 | }) 79 | 80 | it('modify', () => { 81 | const modify = pipe( 82 | value, 83 | _.modify((value) => value * 2) 84 | ) 85 | U.deepStrictEqual(modify(leaf), leaf) 86 | U.deepStrictEqual(modify(node(1, leaf, leaf)), node(2, leaf, leaf)) 87 | }) 88 | 89 | it('modifyOption', () => { 90 | const modifyOption = pipe( 91 | value, 92 | _.modifyOption((value) => value * 2) 93 | ) 94 | U.deepStrictEqual(modifyOption(leaf), O.none) 95 | U.deepStrictEqual(modifyOption(node(1, leaf, leaf)), O.some(node(2, leaf, leaf))) 96 | }) 97 | 98 | it('prop', () => { 99 | type S = O.Option<{ 100 | readonly a: string 101 | readonly b: number 102 | }> 103 | const sa = pipe(_.id(), _.some, _.prop('a')) 104 | U.deepStrictEqual(sa.getOption(O.none), O.none) 105 | U.deepStrictEqual(sa.getOption(O.some({ a: 'a', b: 1 })), O.some('a')) 106 | }) 107 | 108 | it('props', () => { 109 | type S = O.Option<{ 110 | readonly a: string 111 | readonly b: number 112 | readonly c: boolean 113 | }> 114 | const sa = pipe(_.id(), _.some, _.props('a', 'b')) 115 | U.deepStrictEqual(sa.getOption(O.none), O.none) 116 | U.deepStrictEqual(sa.getOption(O.some({ a: 'a', b: 1, c: true })), O.some({ a: 'a', b: 1 })) 117 | }) 118 | 119 | it('component', () => { 120 | type S = O.Option 121 | const sa = pipe(_.id(), _.some, _.component(1)) 122 | U.deepStrictEqual(sa.getOption(O.none), O.none) 123 | U.deepStrictEqual(sa.getOption(O.some(['a', 1])), O.some(1)) 124 | }) 125 | 126 | it('index', () => { 127 | type S = ReadonlyArray 128 | const optional = pipe(_.id(), _.index(0)) 129 | U.deepStrictEqual(optional.getOption([]), O.none) 130 | U.deepStrictEqual(optional.getOption([1]), O.some(1)) 131 | U.deepStrictEqual(optional.set(2)([]), []) 132 | U.deepStrictEqual(optional.set(2)([1]), [2]) 133 | // should return the same reference 134 | const empty: S = [] 135 | const full: S = [1] 136 | assert.strictEqual(optional.set(1)(empty), empty) 137 | assert.strictEqual(optional.set(1)(full), full) 138 | }) 139 | 140 | it('indexNonEmpty', () => { 141 | type S = ReadonlyNonEmptyArray 142 | const optional = pipe(_.id(), _.indexNonEmpty(1)) 143 | U.deepStrictEqual(optional.getOption([1, 2]), O.some(2)) 144 | U.deepStrictEqual(optional.set(3)([1]), [1]) 145 | U.deepStrictEqual(optional.set(3)([1, 2]), [1, 3]) 146 | // should return the same reference 147 | const full: S = [1, 2] 148 | assert.strictEqual(optional.set(2)(full), full) 149 | }) 150 | 151 | it('key', () => { 152 | const sa = pipe(_.id>(), _.key('k')) 153 | U.deepStrictEqual(sa.getOption({ k: 1, j: 2 }), O.some(1)) 154 | }) 155 | 156 | it('compose', () => { 157 | type S = O.Option 158 | const sa = pipe(_.id(), _.some) 159 | const ab = value 160 | const sb = pipe(sa, _.compose(ab)) 161 | U.deepStrictEqual(sb.getOption(O.none), O.none) 162 | U.deepStrictEqual(sb.getOption(O.some(leaf)), O.none) 163 | U.deepStrictEqual(sb.getOption(O.some(node(1, leaf, leaf))), O.some(1)) 164 | U.deepStrictEqual(sb.reverseGet(1), O.some(node(1, leaf, leaf))) 165 | }) 166 | 167 | it('composeOptional', () => { 168 | type S = O.Option 169 | const sa = pipe(_.id(), _.some) 170 | const ab: Op.Optional = Op.optional( 171 | (s) => (s.length > 0 ? O.some(s[0]) : O.none), 172 | (a) => (s) => (s.length > 0 ? a + s.substring(1) : s) 173 | ) 174 | const sb = pipe(sa, _.composeOptional(ab)) 175 | U.deepStrictEqual(sb.getOption(O.none), O.none) 176 | U.deepStrictEqual(sb.getOption(O.some('')), O.none) 177 | U.deepStrictEqual(sb.getOption(O.some('ab')), O.some('a')) 178 | U.deepStrictEqual(sb.set('c')(O.none), O.none) 179 | U.deepStrictEqual(sb.set('c')(O.some('')), O.some('')) 180 | U.deepStrictEqual(sb.set('c')(O.some('ab')), O.some('cb')) 181 | }) 182 | 183 | it('composeTraversal', () => { 184 | type S = O.Option> 185 | const sa = pipe(_.id(), _.some) 186 | const ab = T.fromTraversable(A.readonlyArray)() 187 | const sb = pipe(sa, _.composeTraversal(ab)) 188 | U.deepStrictEqual(sb.modifyF(Id.identity)((n) => n * 2)(O.none), O.none) 189 | U.deepStrictEqual(sb.modifyF(Id.identity)((n) => n * 2)(O.some([1, 2, 3])), O.some([2, 4, 6])) 190 | }) 191 | 192 | it('right', () => { 193 | type S = E.Either 194 | const sa = pipe(_.id(), _.right) 195 | U.deepStrictEqual(sa.getOption(E.right(1)), O.some(1)) 196 | U.deepStrictEqual(sa.getOption(E.left('a')), O.none) 197 | U.deepStrictEqual(sa.reverseGet(2), E.right(2)) 198 | }) 199 | 200 | it('left', () => { 201 | type S = E.Either 202 | const sa = pipe(_.id(), _.left) 203 | U.deepStrictEqual(sa.getOption(E.right(1)), O.none) 204 | U.deepStrictEqual(sa.getOption(E.left('a')), O.some('a')) 205 | U.deepStrictEqual(sa.reverseGet('b'), E.left('b')) 206 | }) 207 | 208 | it('atKey', () => { 209 | type S = ReadonlyRecord 210 | const sa = pipe(_.id(), _.atKey('a')) 211 | U.deepStrictEqual(sa.getOption({ a: 1 }), O.some(O.some(1))) 212 | U.deepStrictEqual(sa.set(O.some(2))({ a: 1, b: 2 }), { a: 2, b: 2 }) 213 | U.deepStrictEqual(sa.set(O.some(1))({ b: 2 }), { a: 1, b: 2 }) 214 | U.deepStrictEqual(sa.set(O.none)({ a: 1, b: 2 }), { b: 2 }) 215 | }) 216 | 217 | it('filter', () => { 218 | type S = O.Option 219 | const sa = pipe( 220 | _.id(), 221 | _.some, 222 | _.filter((n) => n > 0) 223 | ) 224 | U.deepStrictEqual(sa.getOption(O.some(1)), O.some(1)) 225 | U.deepStrictEqual(sa.getOption(O.some(-1)), O.none) 226 | U.deepStrictEqual(sa.getOption(O.none), O.none) 227 | U.deepStrictEqual(sa.reverseGet(2), O.some(2)) 228 | U.deepStrictEqual(sa.reverseGet(-1), O.some(-1)) 229 | }) 230 | 231 | it('findFirst', () => { 232 | type S = O.Option> 233 | const optional = pipe( 234 | _.id(), 235 | _.some, 236 | _.findFirst((n) => n > 0) 237 | ) 238 | U.deepStrictEqual(optional.getOption(O.none), O.none) 239 | U.deepStrictEqual(optional.getOption(O.some([])), O.none) 240 | U.deepStrictEqual(optional.getOption(O.some([-1, -2, -3])), O.none) 241 | U.deepStrictEqual(optional.getOption(O.some([-1, 2, -3])), O.some(2)) 242 | U.deepStrictEqual(optional.set(3)(O.none), O.none) 243 | U.deepStrictEqual(optional.set(3)(O.some([])), O.some([])) 244 | U.deepStrictEqual(optional.set(3)(O.some([-1, -2, -3])), O.some([-1, -2, -3])) 245 | U.deepStrictEqual(optional.set(3)(O.some([-1, 2, -3])), O.some([-1, 3, -3])) 246 | U.deepStrictEqual(optional.set(4)(O.some([-1, -2, 3])), O.some([-1, -2, 4])) 247 | }) 248 | 249 | it('findFirstNonEmpty', () => { 250 | type S = O.Option> 251 | const optional = pipe( 252 | _.id(), 253 | _.some, 254 | _.findFirstNonEmpty((n) => n > 0) 255 | ) 256 | U.deepStrictEqual(optional.getOption(O.none), O.none) 257 | U.deepStrictEqual(optional.getOption(O.some([-1, -2, -3])), O.none) 258 | U.deepStrictEqual(optional.getOption(O.some([-1, 2, -3])), O.some(2)) 259 | U.deepStrictEqual(optional.set(3)(O.none), O.none) 260 | U.deepStrictEqual(optional.set(3)(O.some([-1, -2, -3])), O.some([-1, -2, -3] as const)) 261 | U.deepStrictEqual(optional.set(3)(O.some([-1, 2, -3])), O.some([-1, 3, -3] as const)) 262 | U.deepStrictEqual(optional.set(4)(O.some([-1, -2, 3])), O.some([-1, -2, 4] as const)) 263 | }) 264 | 265 | it('traverse', () => { 266 | type S = O.Option> 267 | const sa = pipe(_.id(), _.some, _.traverse(A.readonlyArray)) 268 | const modify = pipe( 269 | sa, 270 | T.modify((s) => s.toUpperCase()) 271 | ) 272 | U.deepStrictEqual(modify(O.some(['a'])), O.some(['A'])) 273 | }) 274 | 275 | it('fromNullable', () => { 276 | type S = O.Option 277 | const sa = pipe(_.id(), _.some, _.fromNullable) 278 | U.deepStrictEqual(sa.getOption(O.none), O.none) 279 | U.deepStrictEqual(sa.getOption(O.some(undefined)), O.none) 280 | U.deepStrictEqual(sa.getOption(O.some(1)), O.some(1)) 281 | U.deepStrictEqual(sa.reverseGet(1), O.some(1)) 282 | }) 283 | 284 | it('modifyF', () => { 285 | const f = pipe( 286 | value, 287 | _.modifyF(O.option)((n) => (n > 0 ? O.some(n * 2) : O.none)) 288 | ) 289 | U.deepStrictEqual(f(node(1, leaf, leaf)), O.some(node(2, leaf, leaf))) 290 | U.deepStrictEqual(f(leaf), O.some(leaf)) 291 | U.deepStrictEqual(f(node(-1, leaf, leaf)), O.none) 292 | }) 293 | }) 294 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | > **Tags:** 4 | > 5 | > - [New Feature] 6 | > - [Bug Fix] 7 | > - [Breaking Change] 8 | > - [Documentation] 9 | > - [Internal] 10 | > - [Polish] 11 | > - [Experimental] 12 | 13 | **Note**: Gaps between patch versions are faulty/broken releases. **Note**: A feature tagged as Experimental is in a 14 | high state of flux, you're at risk of it changing without notice. 15 | 16 | # 2.3.13 17 | 18 | - **Polish** 19 | - improve `modify` / `modifyOption` behaviour when using `pipe`, #181 (@thewilkybarkid) 20 | 21 | # 2.3.12 22 | 23 | - **Polish** 24 | - Add missing pure annotations, #175 (@OliverJAsh) 25 | 26 | # 2.3.11 27 | 28 | - **Bug Fix** 29 | - OptionalFromPath: Type Issue fix for 5 arguments, #167 (@Barackos) 30 | 31 | # 2.3.10 32 | 33 | - **Internal** 34 | - optimize `fromTraversable`, closes #119 (@gcanti) 35 | 36 | # 2.3.9 37 | 38 | Experimental modules require `fp-ts@^2.5.0`. 39 | 40 | - **Experimental** 41 | - `At` 42 | - add `at` constructor (@gcanti) 43 | - `Iso` 44 | - add `iso` constructor (@gcanti) 45 | - add `composeLens` (@gcanti) 46 | - add `composePrism` (@gcanti) 47 | - add `composeOptional` (@gcanti) 48 | - add `composeTraversal` (@gcanti) 49 | - add `fromNullable` (@gcanti) 50 | - add `filter` (@gcanti) 51 | - add `fromNullable` (@gcanti) 52 | - add `prop` (@gcanti) 53 | - add `props` (@gcanti) 54 | - add `component` (@gcanti) 55 | - add `index` (@gcanti) 56 | - add `indexNonEmpty` (@gcanti) 57 | - add `key` (@gcanti) 58 | - add `atKey` (@gcanti) 59 | - add `some` (@gcanti) 60 | - add `right` (@gcanti) 61 | - add `left` (@gcanti) 62 | - add `traverse` (@gcanti) 63 | - add `findFirst` (@gcanti) 64 | - add `findFirstNonEmpty` (@gcanti) 65 | - add `composeIso` (@gcanti) 66 | - add `Semigroupoid` (@gcanti) 67 | - (\*) rename `invariantIso` to `Invariant` (@gcanti) 68 | - (\*) rename `categoryIso` to `Category` (@gcanti) 69 | - `Ix` 70 | - add `index` constructor (@gcanti) 71 | - add `indexReadonlyNonEmptyArray` (@gcanti) 72 | - `Lens` 73 | - add `lens` constructor (@gcanti) 74 | - add `composeIso` (@gcanti) 75 | - add `composeTraversal` (@gcanti) 76 | - add `indexNonEmpty` (@gcanti) 77 | - add `findFirstNonEmpty` (@gcanti) 78 | - add `composeLens` (@gcanti) 79 | - add `Semigroupoid` (@gcanti) 80 | - (\*) rename `invariantIso` to `Invariant` (@gcanti) 81 | - (\*) rename `categoryIso` to `Category` (@gcanti) 82 | - `Prism` 83 | - add `prism` constructor (@gcanti) 84 | - add `composeIso` (@gcanti) 85 | - add `composeTraversal` (@gcanti) 86 | - add `indexNonEmpty` (@gcanti) 87 | - add `findFirstNonEmpty` (@gcanti) 88 | - add `composePrism` (@gcanti) 89 | - add `Semigroupoid` (@gcanti) 90 | - (\*) rename `invariantIso` to `Invariant` (@gcanti) 91 | - (\*) rename `categoryIso` to `Category` (@gcanti) 92 | - `Optional` 93 | - add `optional` constructor (@gcanti) 94 | - add `composeIso` (@gcanti) 95 | - add `composeTraversal` (@gcanti) 96 | - add `indexNonEmpty` (@gcanti) 97 | - add `findFirstNonEmpty` (@gcanti) 98 | - add `composeOptional` (@gcanti) 99 | - add `Semigroupoid` (@gcanti) 100 | - (\*) rename `invariantIso` to `Invariant` (@gcanti) 101 | - (\*) rename `categoryIso` to `Category` (@gcanti) 102 | - `Traversal` 103 | - add `traversal` constructor (@gcanti) 104 | - add `composeIso` (@gcanti) 105 | - add `composeLens` (@gcanti) 106 | - add `composePrism` (@gcanti) 107 | - add `composeOptional` (@gcanti) 108 | - add `findFirst` (@gcanti) 109 | - add `findFirstNonEmpty` (@gcanti) 110 | - add `fromNullable` (@gcanti) 111 | - add `indexNonEmpty` (@gcanti) 112 | - add `composeTraversal` (@gcanti) 113 | - add `Semigroupoid` (@gcanti) 114 | - (\*) rename `categoryIso` to `Category` (@gcanti) 115 | 116 | (\*) breaking change 117 | 118 | # 2.3.7 119 | 120 | - **Experimental** 121 | - `At` 122 | - add `atReadonlyRecord` (@gcanti) 123 | - add `atReadonlyMap` (@gcanti) 124 | - add `atReadonlySet` (@gcanti) 125 | - deprecate `atRecord` (@gcanti) 126 | - `Ix` 127 | - add `indexReadonlyRecord` (@gcanti) 128 | - add `indexReadonlyArray` (@gcanti) 129 | - add `indexReadonlyMap` (@gcanti) 130 | - deprecate `indexRecord` (@gcanti) 131 | - deprecate `indexArray` (@gcanti) 132 | - `Optional` 133 | - add `composeLens` (@gcanti) 134 | - add `composePrism` (@gcanti) 135 | - add `setOption` (@gcanti) 136 | 137 | # 2.3.6 138 | 139 | - **Polish** 140 | - import without `/lib` or `/es6` paths, closes #147 (@gcanti) 141 | 142 | # 2.3.5 143 | 144 | - **Experimental** 145 | - add `modifyF`, closes #149 (@gcanti) 146 | - `Iso` 147 | - `Lens` 148 | - `Prism` 149 | - `Optional` 150 | 151 | # 2.3.4 152 | 153 | - **Experimental** 154 | - `Lens`, `Prism`, `Optional`: add `Refinement` overload to `findFirst`, #148 (@wmaurer) 155 | 156 | # 2.3.3 157 | 158 | - **Experimental** 159 | - `Prism` 160 | - (\*) remove `fromSome` constructor (@gcanti) 161 | - (\*) change `fromNullable` signature (@gcanti) 162 | - `Optional` 163 | - add missing `fromNullable` combinator, closes #133 (@gcanti) 164 | 165 | (\*) breaking change 166 | 167 | # 2.3.2 168 | 169 | - **Experimental** 170 | - `At` 171 | - add `atRecord` (@gcanti) 172 | - `Ix` 173 | - add `indexArray` (@gcanti) 174 | - add `indexRecord` (@gcanti) 175 | - `Lens` 176 | - add `findFirst` combinator, closes #131 (@gcanti) 177 | - `Prism` 178 | - add `findFirst` combinator (@gcanti) 179 | - add `traverse` combinator (@gcanti) 180 | - `Optional` 181 | - add `findFirst` combinator (@gcanti) 182 | - add `traverse` combinator (@gcanti) 183 | 184 | # 2.3.1 185 | 186 | - **Experimental** 187 | - add `Iso` module (@gcanti) 188 | - add `Lens` module (@gcanti) 189 | - add `Prism` module (@gcanti) 190 | - add `Optional` module (@gcanti) 191 | - add `Traversal` module (@gcanti) 192 | - add `At` module (@gcanti) 193 | - add `Ix` module (@gcanti) 194 | - **Internal** 195 | - implement old APIs through new APIs (@gcanti) 196 | 197 | # 2.2.0 198 | 199 | - **New Feature** 200 | - `At` 201 | - add `ReadonlyRecord` module (@gcanti) 202 | - add `ReadonlySet` module (@gcanti) 203 | - `Index` 204 | - add `ReadonlyArray` module (@gcanti) 205 | - add `ReadonlyNonEmptyArray` module (@gcanti) 206 | - add `ReadonlyRecord` module (@gcanti) 207 | 208 | # 2.1.1 209 | 210 | - **Bug Fix** 211 | - fix `Optional.fromPath`, #122 (@mikearnaldi) 212 | 213 | # 2.1.0 214 | 215 | - **New Feature** 216 | - Add support for `Optional.fromPath`, #105 (@cybermaak) 217 | - **Bug Fix** 218 | - handle nullable values in `fromNullableProp`, fix #106 (@gcanti) 219 | 220 | # 2.0.1 221 | 222 | - **Bug Fix** 223 | - rewrite es6 imports (@gcanti) 224 | 225 | # 2.0.0 226 | 227 | - **Breaking Change** 228 | - upgrade to `fp-ts@2.x` (@gcanti) 229 | - remove deprecated APIs (@gcanti) 230 | - uncurried `Lens.fromProp` 231 | - uncurried `Lens.fromProps` 232 | - uncurried `Lens.fromNullableProp` 233 | - uncurried `Optional.fromNullableProp` 234 | - uncurried `Optional.fromOptionProp` 235 | - remove `At/StrMap` (@gcanti) 236 | - remove `Index/StrMap` (@gcanti) 237 | 238 | # 1.7.2 239 | 240 | output ES modules to better support tree-shaking (@gcanti) 241 | 242 | # 1.7.1 243 | 244 | - **Polish** 245 | - move `fp-ts` to peerDependencies (@gcanti) 246 | 247 | # 1.7.0 248 | 249 | - **New Feature** 250 | - add `At/Record` (@mlegenhausen) 251 | - add `Index/Record` (@mlegenhausen) 252 | 253 | # 1.6.1 254 | 255 | - **Polish** 256 | - Providing a `Refinement` to the `find` method of `Fold` will return the narrowed type (@Stouffi) 257 | 258 | # 1.6.0 259 | 260 | - **New Feature** 261 | - add `Traversal.prototype.filter` (@gcanti) 262 | - add `Either` prisms (@gcanti) 263 | - **Polish** 264 | - many optimizitions (@sledorze) 265 | - **Deprecation** 266 | - deprecate `Prism.fromRefinement` in favour of `Prism.fromPredicate` (@gcanti) 267 | 268 | # 1.5.3 269 | 270 | - **Bug Fix** 271 | - revert 27b587b, closes #75 (@gcanti) 272 | 273 | # 1.5.2 274 | 275 | - **Polish** 276 | - disallow improper use of `from`\* APIs, closes #73 (@gcanti) 277 | 278 | # 1.5.1 279 | 280 | - **Polish** 281 | - add aliases for `compose` methods, closes #51 (@gcanti) 282 | 283 | # 1.5.0 284 | 285 | - **New Feature** 286 | - add `indexNonEmptyArray` (@sledorze) 287 | - **Internal** 288 | - upgrade to `fp-ts@1.11.0` (@sledorze) 289 | 290 | **Note**. If you are running `< typescript@3.0.1` you have to polyfill `unknown`. 291 | 292 | You can use [unknown-ts](https://github.com/gcanti/unknown-ts) as a polyfill. 293 | 294 | # 1.4.1 295 | 296 | - **New Feature** 297 | - add `Prism.fromRefinement` (@bepremeg) 298 | - add `Optional.fromOptionProp` (@bepremeg) 299 | 300 | # 1.3.0 301 | 302 | - **New Feature** 303 | - add `Lens.{fromProp, fromPath, fromNullableProp}` and `Optional.fromNullableProp` curried overloadings (@gcanti) 304 | 305 | # 1.2.0 306 | 307 | - **New Feature** 308 | - add `At` (@leighman) 309 | - add `Set` instance 310 | - add `StrMap` instance 311 | - add `Index` (@leighman) 312 | - add `Array` instance 313 | - add `StrMap` instance 314 | 315 | # 1.1.0 316 | 317 | - **New Feature** 318 | - add `Lens.fromProps` (@gcanti) 319 | 320 | # 1.0.0 321 | 322 | - **Breaking Change** 323 | - upgrade to `fp-ts@1.0.0` (@gcanti) 324 | 325 | # 0.5.2 326 | 327 | - **New Feature** 328 | - add `Iso.reverse`, closes #36 (@gcanti) 329 | 330 | # 0.5.1 331 | 332 | - **Experimental** 333 | - add Flowtype support (@gcanti) 334 | 335 | # 0.5.0 336 | 337 | - **Breaking Change** 338 | - upgrade to fp-ts 0.6 (@gcanti) 339 | 340 | # 0.4.4 341 | 342 | - **New Feature** 343 | - add `Lens.fromNullableProp` (@gcanti) 344 | 345 | # 0.4.3 346 | 347 | - **New Feature** 348 | - `Prism`: add `set` method (@gcanti) 349 | - `Optional`: add `fromNullableProps` static function (@gcanti) 350 | - **Bug fix** 351 | - `Prism`: change `asOptional`, `asTraversal` implementations, fix #29 (@gcanti) 352 | 353 | # 0.4.2 354 | 355 | - **Polish** 356 | - fix Optional laws (@gcanti) 357 | - remove `Endomorphism` type alias (@gcanti) 358 | 359 | # 0.4.1 360 | 361 | - **New Feature** 362 | - Add aliases to ISO, closes #24 (@gcanti) 363 | 364 | # 0.4.0 365 | 366 | - **Breaking Change** 367 | - upgrade to fp-ts 0.5 (@gcanti) 368 | - currying of all APIs (@gcanti) 369 | 370 | # 0.3.2 371 | 372 | - **Polish** 373 | - upgrade to latest fp-ts (0.4.3) (@gcanti) 374 | 375 | # 0.3.1 376 | 377 | - **New Feature** 378 | - Added Setter (@LiamGoodacre) 379 | - Added Getter (@LiamGoodacre) 380 | - Added all possible conversions (e.g asGetter, asFold, etc) (@LiamGoodacre) 381 | - Added all possible compositions (@LiamGoodacre) 382 | - add \_tag fields (allows for tagged unions) (@gcanti) 383 | - **Polish** 384 | - Fixed some typos (Options/Option -> Optional) (@LiamGoodacre) 385 | - Minor rearrangement so conversions and compositions are grouped (@LiamGoodacre) 386 | 387 | # 0.3.0 388 | 389 | - **Breaking Change** 390 | - upgrade to latest fp-ts (@gcanti) 391 | 392 | # 0.2.0 393 | 394 | - **New Feature** 395 | - add `Prism.some`, closes #10 (@gcanti) 396 | - add `composeX`, closes #11 (@gcanti) 397 | - **Breaking Change** 398 | - upgrade to latest `fp-ts` (`monocle-ts` APIs are not changed though) (@gcanti) 399 | - drop `lib-jsnext` folder 400 | - remove `Optional.fromProp`, closes #9 (@gcanti) 401 | 402 | # 0.1.1 403 | 404 | - **New Feature** 405 | - add `Prism.fromPredicate` 406 | - fix `Optional.fromProp` 407 | 408 | # 0.1.0 409 | 410 | Initial release 411 | -------------------------------------------------------------------------------- /docs/modules/Iso.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Iso.ts 3 | nav_order: 15 4 | parent: Modules 5 | --- 6 | 7 | ## Iso overview 8 | 9 | **This module is experimental** 10 | 11 | Experimental features are published in order to get early feedback from the community. 12 | 13 | A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 14 | 15 | An `Iso` is an optic which converts elements of type `S` into elements of type `A` without loss. 16 | 17 | Laws: 18 | 19 | 1. `reverseGet(get(s)) = s` 20 | 2. `get(reversetGet(a)) = a` 21 | 22 | Added in v2.3.0 23 | 24 | --- 25 | 26 |

Table of contents

27 | 28 | - [Invariant](#invariant) 29 | - [imap](#imap) 30 | - [combinators](#combinators) 31 | - [atKey](#atkey) 32 | - [component](#component) 33 | - [filter](#filter) 34 | - [findFirst](#findfirst) 35 | - [findFirstNonEmpty](#findfirstnonempty) 36 | - [fromNullable](#fromnullable) 37 | - [index](#index) 38 | - [indexNonEmpty](#indexnonempty) 39 | - [key](#key) 40 | - [left](#left) 41 | - [modify](#modify) 42 | - [modifyF](#modifyf) 43 | - [prop](#prop) 44 | - [props](#props) 45 | - [right](#right) 46 | - [some](#some) 47 | - [traverse](#traverse) 48 | - [compositions](#compositions) 49 | - [compose](#compose) 50 | - [composeIso](#composeiso) 51 | - [composeLens](#composelens) 52 | - [composeOptional](#composeoptional) 53 | - [composePrism](#composeprism) 54 | - [composeTraversal](#composetraversal) 55 | - [constructors](#constructors) 56 | - [id](#id) 57 | - [iso](#iso) 58 | - [reverse](#reverse) 59 | - [converters](#converters) 60 | - [asLens](#aslens) 61 | - [asOptional](#asoptional) 62 | - [asPrism](#asprism) 63 | - [asTraversal](#astraversal) 64 | - [instances](#instances) 65 | - [Category](#category) 66 | - [Invariant](#invariant-1) 67 | - [Semigroupoid](#semigroupoid) 68 | - [URI](#uri) 69 | - [URI (type alias)](#uri-type-alias) 70 | - [model](#model) 71 | - [Iso (interface)](#iso-interface) 72 | 73 | --- 74 | 75 | # Invariant 76 | 77 | ## imap 78 | 79 | **Signature** 80 | 81 | ```ts 82 | export declare const imap: (f: (a: A) => B, g: (b: B) => A) => (sa: Iso) => Iso 83 | ``` 84 | 85 | Added in v2.3.0 86 | 87 | # combinators 88 | 89 | ## atKey 90 | 91 | Return a `Lens` from a `Iso` focused on a required key of a `ReadonlyRecord`. 92 | 93 | **Signature** 94 | 95 | ```ts 96 | export declare const atKey: (key: string) => (sa: Iso>>) => Lens> 97 | ``` 98 | 99 | Added in v2.3.8 100 | 101 | ## component 102 | 103 | Return a `Lens` from a `Iso` focused on a component of a tuple. 104 | 105 | **Signature** 106 | 107 | ```ts 108 | export declare const component:
( 109 | prop: P 110 | ) => (sa: Iso) => Lens 111 | ``` 112 | 113 | Added in v2.3.8 114 | 115 | ## filter 116 | 117 | **Signature** 118 | 119 | ```ts 120 | export declare function filter(refinement: Refinement): (sa: Iso) => Prism 121 | export declare function filter(predicate: Predicate): (sa: Iso) => Prism 122 | ``` 123 | 124 | Added in v2.3.8 125 | 126 | ## findFirst 127 | 128 | **Signature** 129 | 130 | ```ts 131 | export declare function findFirst( 132 | refinement: Refinement 133 | ): (sa: Iso>) => Optional 134 | export declare function findFirst(predicate: Predicate): (sa: Iso>) => Optional 135 | ``` 136 | 137 | Added in v2.3.8 138 | 139 | ## findFirstNonEmpty 140 | 141 | **Signature** 142 | 143 | ```ts 144 | export declare function findFirstNonEmpty( 145 | refinement: Refinement 146 | ): (sa: Iso>) => Optional 147 | export declare function findFirstNonEmpty( 148 | predicate: Predicate 149 | ): (sa: Iso>) => Optional 150 | ``` 151 | 152 | Added in v2.3.8 153 | 154 | ## fromNullable 155 | 156 | Return a `Prism` from a `Iso` focused on a nullable value. 157 | 158 | **Signature** 159 | 160 | ```ts 161 | export declare const fromNullable: (sa: Iso) => Prism> 162 | ``` 163 | 164 | Added in v2.3.8 165 | 166 | ## index 167 | 168 | Return a `Optional` from a `Iso` focused on an index of a `ReadonlyArray`. 169 | 170 | **Signature** 171 | 172 | ```ts 173 | export declare const index: (i: number) => (sa: Iso) => Optional 174 | ``` 175 | 176 | Added in v2.3.8 177 | 178 | ## indexNonEmpty 179 | 180 | Return a `Optional` from a `Iso` focused on an index of a `ReadonlyNonEmptyArray`. 181 | 182 | **Signature** 183 | 184 | ```ts 185 | export declare const indexNonEmpty: (i: number) => (sa: Iso>) => Optional 186 | ``` 187 | 188 | Added in v2.3.8 189 | 190 | ## key 191 | 192 | Return a `Optional` from a `Iso` focused on a key of a `ReadonlyRecord`. 193 | 194 | **Signature** 195 | 196 | ```ts 197 | export declare const key: (key: string) => (sa: Iso>>) => Optional 198 | ``` 199 | 200 | Added in v2.3.8 201 | 202 | ## left 203 | 204 | Return a `Prism` from a `Iso` focused on the `Left` of a `Either` type. 205 | 206 | **Signature** 207 | 208 | ```ts 209 | export declare const left: (sea: Iso>) => Prism 210 | ``` 211 | 212 | Added in v2.3.8 213 | 214 | ## modify 215 | 216 | **Signature** 217 | 218 | ```ts 219 | export declare const modify: (f: (a: A) => B) => (sa: Iso) => (s: S) => S 220 | ``` 221 | 222 | Added in v2.3.0 223 | 224 | ## modifyF 225 | 226 | **Signature** 227 | 228 | ```ts 229 | export declare function modifyF( 230 | F: Functor3 231 | ): (f: (a: A) => Kind3) => (sa: Iso) => (s: S) => Kind3 232 | export declare function modifyF( 233 | F: Functor2 234 | ): (f: (a: A) => Kind2) => (sa: Iso) => (s: S) => Kind2 235 | export declare function modifyF( 236 | F: Functor1 237 | ): (f: (a: A) => Kind) => (sa: Iso) => (s: S) => Kind 238 | export declare function modifyF( 239 | F: Functor 240 | ): (f: (a: A) => HKT) => (sa: Iso) => (s: S) => HKT 241 | ``` 242 | 243 | Added in v2.3.5 244 | 245 | ## prop 246 | 247 | Return a `Lens` from a `Iso` and a prop. 248 | 249 | **Signature** 250 | 251 | ```ts 252 | export declare const prop: (prop: P) => (sa: Iso) => Lens 253 | ``` 254 | 255 | Added in v2.3.8 256 | 257 | ## props 258 | 259 | Return a `Lens` from a `Iso` and a list of props. 260 | 261 | **Signature** 262 | 263 | ```ts 264 | export declare const props: ( 265 | props_0: P, 266 | props_1: P, 267 | ...props_2: P[] 268 | ) => (sa: Iso) => Lens 269 | ``` 270 | 271 | Added in v2.3.8 272 | 273 | ## right 274 | 275 | Return a `Prism` from a `Iso` focused on the `Right` of a `Either` type. 276 | 277 | **Signature** 278 | 279 | ```ts 280 | export declare const right: (sea: Iso>) => Prism 281 | ``` 282 | 283 | Added in v2.3.8 284 | 285 | ## some 286 | 287 | Return a `Prism` from a `Iso` focused on the `Some` of a `Option` type. 288 | 289 | **Signature** 290 | 291 | ```ts 292 | export declare const some: (soa: Iso>) => Prism 293 | ``` 294 | 295 | Added in v2.3.8 296 | 297 | ## traverse 298 | 299 | Return a `Traversal` from a `Iso` focused on a `Traversable`. 300 | 301 | **Signature** 302 | 303 | ```ts 304 | export declare function traverse(T: Traversable1): (sta: Iso>) => Traversal 305 | ``` 306 | 307 | Added in v2.3.8 308 | 309 | # compositions 310 | 311 | ## compose 312 | 313 | Compose an `Iso` with an `Iso`. 314 | 315 | **Signature** 316 | 317 | ```ts 318 | export declare const compose: (ab: Iso) => (sa: Iso) => Iso 319 | ``` 320 | 321 | Added in v2.3.0 322 | 323 | ## composeIso 324 | 325 | Alias of `compose`. 326 | 327 | **Signature** 328 | 329 | ```ts 330 | export declare const composeIso: (ab: Iso) => (sa: Iso) => Iso 331 | ``` 332 | 333 | Added in v2.3.8 334 | 335 | ## composeLens 336 | 337 | Compose an `Iso` with a `Lens`. 338 | 339 | **Signature** 340 | 341 | ```ts 342 | export declare const composeLens: (ab: Lens) => (sa: Iso) => Lens 343 | ``` 344 | 345 | Added in v2.3.8 346 | 347 | ## composeOptional 348 | 349 | Compose an `Iso` with a `Optional`. 350 | 351 | **Signature** 352 | 353 | ```ts 354 | export declare const composeOptional: (ab: Optional) => (sa: Iso) => Optional 355 | ``` 356 | 357 | Added in v2.3.8 358 | 359 | ## composePrism 360 | 361 | Compose an `Iso` with a `Prism`. 362 | 363 | **Signature** 364 | 365 | ```ts 366 | export declare const composePrism: (ab: Prism) => (sa: Iso) => Prism 367 | ``` 368 | 369 | Added in v2.3.8 370 | 371 | ## composeTraversal 372 | 373 | Compose an `Iso` with a `Traversal`. 374 | 375 | **Signature** 376 | 377 | ```ts 378 | export declare const composeTraversal: (ab: Traversal) => (sa: Iso) => Traversal 379 | ``` 380 | 381 | Added in v2.3.8 382 | 383 | # constructors 384 | 385 | ## id 386 | 387 | **Signature** 388 | 389 | ```ts 390 | export declare const id: () => Iso 391 | ``` 392 | 393 | Added in v2.3.0 394 | 395 | ## iso 396 | 397 | **Signature** 398 | 399 | ```ts 400 | export declare const iso: (get: (s: S) => A, reverseGet: (a: A) => S) => Iso 401 | ``` 402 | 403 | Added in v2.3.8 404 | 405 | ## reverse 406 | 407 | **Signature** 408 | 409 | ```ts 410 | export declare const reverse: (sa: Iso) => Iso 411 | ``` 412 | 413 | Added in v2.3.0 414 | 415 | # converters 416 | 417 | ## asLens 418 | 419 | View an `Iso` as a `Lens`. 420 | 421 | **Signature** 422 | 423 | ```ts 424 | export declare const asLens: (sa: Iso) => Lens 425 | ``` 426 | 427 | Added in v2.3.0 428 | 429 | ## asOptional 430 | 431 | View an `Iso` as a `Optional`. 432 | 433 | **Signature** 434 | 435 | ```ts 436 | export declare const asOptional: (sa: Iso) => Optional 437 | ``` 438 | 439 | Added in v2.3.0 440 | 441 | ## asPrism 442 | 443 | View an `Iso` as a `Prism`. 444 | 445 | **Signature** 446 | 447 | ```ts 448 | export declare const asPrism: (sa: Iso) => Prism 449 | ``` 450 | 451 | Added in v2.3.0 452 | 453 | ## asTraversal 454 | 455 | View an `Iso` as a `Traversal`. 456 | 457 | **Signature** 458 | 459 | ```ts 460 | export declare const asTraversal: (sa: Iso) => Traversal 461 | ``` 462 | 463 | Added in v2.3.0 464 | 465 | # instances 466 | 467 | ## Category 468 | 469 | **Signature** 470 | 471 | ```ts 472 | export declare const Category: Category2<'monocle-ts/Iso'> 473 | ``` 474 | 475 | Added in v2.3.0 476 | 477 | ## Invariant 478 | 479 | **Signature** 480 | 481 | ```ts 482 | export declare const Invariant: Invariant2<'monocle-ts/Iso'> 483 | ``` 484 | 485 | Added in v2.3.0 486 | 487 | ## Semigroupoid 488 | 489 | **Signature** 490 | 491 | ```ts 492 | export declare const Semigroupoid: Semigroupoid2<'monocle-ts/Iso'> 493 | ``` 494 | 495 | Added in v2.3.8 496 | 497 | ## URI 498 | 499 | **Signature** 500 | 501 | ```ts 502 | export declare const URI: 'monocle-ts/Iso' 503 | ``` 504 | 505 | Added in v2.3.0 506 | 507 | ## URI (type alias) 508 | 509 | **Signature** 510 | 511 | ```ts 512 | export type URI = typeof URI 513 | ``` 514 | 515 | Added in v2.3.0 516 | 517 | # model 518 | 519 | ## Iso (interface) 520 | 521 | **Signature** 522 | 523 | ```ts 524 | export interface Iso { 525 | readonly get: (s: S) => A 526 | readonly reverseGet: (a: A) => S 527 | } 528 | ``` 529 | 530 | Added in v2.3.0 531 | -------------------------------------------------------------------------------- /docs/modules/Prism.ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prism.ts 3 | nav_order: 19 4 | parent: Modules 5 | --- 6 | 7 | ## Prism overview 8 | 9 | **This module is experimental** 10 | 11 | Experimental features are published in order to get early feedback from the community. 12 | 13 | A feature tagged as _Experimental_ is in a high state of flux, you're at risk of it changing without notice. 14 | 15 | A `Prism` is an optic used to select part of a sum type. 16 | 17 | Laws: 18 | 19 | 1. `pipe(getOption(s), fold(() => s, reverseGet)) = s` 20 | 2. `getOption(reverseGet(a)) = some(a)` 21 | 22 | Added in v2.3.0 23 | 24 | --- 25 | 26 |

Table of contents

27 | 28 | - [Invariant](#invariant) 29 | - [imap](#imap) 30 | - [combinators](#combinators) 31 | - [atKey](#atkey) 32 | - [component](#component) 33 | - [filter](#filter) 34 | - [findFirst](#findfirst) 35 | - [findFirstNonEmpty](#findfirstnonempty) 36 | - [fromNullable](#fromnullable) 37 | - [index](#index) 38 | - [indexNonEmpty](#indexnonempty) 39 | - [key](#key) 40 | - [left](#left) 41 | - [modify](#modify) 42 | - [modifyF](#modifyf) 43 | - [modifyOption](#modifyoption) 44 | - [prop](#prop) 45 | - [props](#props) 46 | - [right](#right) 47 | - [set](#set) 48 | - [some](#some) 49 | - [traverse](#traverse) 50 | - [compositions](#compositions) 51 | - [compose](#compose) 52 | - [composeIso](#composeiso) 53 | - [composeLens](#composelens) 54 | - [composeOptional](#composeoptional) 55 | - [composePrism](#composeprism) 56 | - [composeTraversal](#composetraversal) 57 | - [constructors](#constructors) 58 | - [fromPredicate](#frompredicate) 59 | - [id](#id) 60 | - [prism](#prism) 61 | - [converters](#converters) 62 | - [asOptional](#asoptional) 63 | - [asTraversal](#astraversal) 64 | - [instances](#instances) 65 | - [Category](#category) 66 | - [Invariant](#invariant-1) 67 | - [Semigroupoid](#semigroupoid) 68 | - [URI](#uri) 69 | - [URI (type alias)](#uri-type-alias) 70 | - [model](#model) 71 | - [Prism (interface)](#prism-interface) 72 | 73 | --- 74 | 75 | # Invariant 76 | 77 | ## imap 78 | 79 | **Signature** 80 | 81 | ```ts 82 | export declare const imap: (f: (a: A) => B, g: (b: B) => A) => (sa: Prism) => Prism 83 | ``` 84 | 85 | Added in v2.3.0 86 | 87 | # combinators 88 | 89 | ## atKey 90 | 91 | Return a `Optional` from a `Prism` focused on a required key of a `ReadonlyRecord`. 92 | 93 | **Signature** 94 | 95 | ```ts 96 | export declare const atKey: ( 97 | key: string 98 | ) => (sa: Prism>>) => Optional> 99 | ``` 100 | 101 | Added in v2.3.0 102 | 103 | ## component 104 | 105 | Return a `Optional` from a `Prism` focused on a component of a tuple. 106 | 107 | **Signature** 108 | 109 | ```ts 110 | export declare const component:
( 111 | prop: P 112 | ) => (sa: Prism) => Optional 113 | ``` 114 | 115 | Added in v2.3.0 116 | 117 | ## filter 118 | 119 | **Signature** 120 | 121 | ```ts 122 | export declare function filter(refinement: Refinement): (sa: Prism) => Prism 123 | export declare function filter(predicate: Predicate): (sa: Prism) => Prism 124 | ``` 125 | 126 | Added in v2.3.0 127 | 128 | ## findFirst 129 | 130 | **Signature** 131 | 132 | ```ts 133 | export declare function findFirst( 134 | refinement: Refinement 135 | ): (sa: Prism>) => Optional 136 | export declare function findFirst(predicate: Predicate): (sa: Prism>) => Optional 137 | ``` 138 | 139 | Added in v2.3.2 140 | 141 | ## findFirstNonEmpty 142 | 143 | **Signature** 144 | 145 | ```ts 146 | export declare function findFirstNonEmpty( 147 | refinement: Refinement 148 | ): (sa: Prism>) => Optional 149 | export declare function findFirstNonEmpty( 150 | predicate: Predicate 151 | ): (sa: Prism>) => Optional 152 | ``` 153 | 154 | Added in v2.3.8 155 | 156 | ## fromNullable 157 | 158 | Return a `Prism` from a `Prism` focused on a nullable value. 159 | 160 | **Signature** 161 | 162 | ```ts 163 | export declare const fromNullable: (sa: Prism) => Prism> 164 | ``` 165 | 166 | Added in v2.3.3 167 | 168 | ## index 169 | 170 | Return a `Optional` from a `Prism` focused on an index of a `ReadonlyArray`. 171 | 172 | **Signature** 173 | 174 | ```ts 175 | export declare const index: (i: number) => (sa: Prism) => Optional 176 | ``` 177 | 178 | Added in v2.3.0 179 | 180 | ## indexNonEmpty 181 | 182 | Return a `Optional` from a `Prism` focused on an index of a `ReadonlyNonEmptyArray`. 183 | 184 | **Signature** 185 | 186 | ```ts 187 | export declare const indexNonEmpty: (i: number) => (sa: Prism>) => Optional 188 | ``` 189 | 190 | Added in v2.3.8 191 | 192 | ## key 193 | 194 | Return a `Optional` from a `Prism` focused on a key of a `ReadonlyRecord`. 195 | 196 | **Signature** 197 | 198 | ```ts 199 | export declare const key: (key: string) => (sa: Prism>>) => Optional 200 | ``` 201 | 202 | Added in v2.3.0 203 | 204 | ## left 205 | 206 | Return a `Prism` from a `Prism` focused on the `Left` of a `Either` type. 207 | 208 | **Signature** 209 | 210 | ```ts 211 | export declare const left: (sea: Prism>) => Prism 212 | ``` 213 | 214 | Added in v2.3.0 215 | 216 | ## modify 217 | 218 | **Signature** 219 | 220 | ```ts 221 | export declare const modify: (f: (a: A) => B) => (sa: Prism) => (s: S) => S 222 | ``` 223 | 224 | Added in v2.3.0 225 | 226 | ## modifyF 227 | 228 | **Signature** 229 | 230 | ```ts 231 | export declare function modifyF( 232 | F: Applicative3 233 | ): (f: (a: A) => Kind3) => (sa: Prism) => (s: S) => Kind3 234 | export declare function modifyF( 235 | F: Applicative2 236 | ): (f: (a: A) => Kind2) => (sa: Prism) => (s: S) => Kind2 237 | export declare function modifyF( 238 | F: Applicative1 239 | ): (f: (a: A) => Kind) => (sa: Prism) => (s: S) => Kind 240 | export declare function modifyF( 241 | F: Applicative 242 | ): (f: (a: A) => HKT) => (sa: Prism) => (s: S) => HKT 243 | ``` 244 | 245 | Added in v2.3.5 246 | 247 | ## modifyOption 248 | 249 | **Signature** 250 | 251 | ```ts 252 | export declare const modifyOption: (f: (a: A) => B) => (sa: Prism) => (s: S) => O.Option 253 | ``` 254 | 255 | Added in v2.3.0 256 | 257 | ## prop 258 | 259 | Return a `Optional` from a `Prism` and a prop. 260 | 261 | **Signature** 262 | 263 | ```ts 264 | export declare const prop: (prop: P) => (sa: Prism) => Optional 265 | ``` 266 | 267 | Added in v2.3.0 268 | 269 | ## props 270 | 271 | Return a `Optional` from a `Prism` and a list of props. 272 | 273 | **Signature** 274 | 275 | ```ts 276 | export declare const props: ( 277 | props_0: P, 278 | props_1: P, 279 | ...props_2: P[] 280 | ) => (sa: Prism) => Optional 281 | ``` 282 | 283 | Added in v2.3.0 284 | 285 | ## right 286 | 287 | Return a `Prism` from a `Prism` focused on the `Right` of a `Either` type. 288 | 289 | **Signature** 290 | 291 | ```ts 292 | export declare const right: (sea: Prism>) => Prism 293 | ``` 294 | 295 | Added in v2.3.0 296 | 297 | ## set 298 | 299 | **Signature** 300 | 301 | ```ts 302 | export declare const set: (a: A) => (sa: Prism) => (s: S) => S 303 | ``` 304 | 305 | Added in v2.3.0 306 | 307 | ## some 308 | 309 | Return a `Prism` from a `Prism` focused on the `Some` of a `Option` type. 310 | 311 | **Signature** 312 | 313 | ```ts 314 | export declare const some: (soa: Prism>) => Prism 315 | ``` 316 | 317 | Added in v2.3.0 318 | 319 | ## traverse 320 | 321 | Return a `Traversal` from a `Prism` focused on a `Traversable`. 322 | 323 | **Signature** 324 | 325 | ```ts 326 | export declare function traverse( 327 | T: Traversable1 328 | ): (sta: Prism>) => Traversal 329 | ``` 330 | 331 | Added in v2.3.0 332 | 333 | # compositions 334 | 335 | ## compose 336 | 337 | Compose a `Prism` with a `Prism`. 338 | 339 | **Signature** 340 | 341 | ```ts 342 | export declare const compose: (ab: Prism) => (sa: Prism) => Prism 343 | ``` 344 | 345 | Added in v2.3.0 346 | 347 | ## composeIso 348 | 349 | Compose a `Prism` with a `Iso`. 350 | 351 | **Signature** 352 | 353 | ```ts 354 | export declare const composeIso: (ab: Iso) => (sa: Prism) => Prism 355 | ``` 356 | 357 | Added in v2.3.8 358 | 359 | ## composeLens 360 | 361 | Compose a `Prism` with a `Lens`. 362 | 363 | **Signature** 364 | 365 | ```ts 366 | export declare const composeLens: (ab: Lens) => (sa: Prism) => Optional 367 | ``` 368 | 369 | Added in v2.3.0 370 | 371 | ## composeOptional 372 | 373 | Compose a `Prism` with an `Optional`. 374 | 375 | **Signature** 376 | 377 | ```ts 378 | export declare const composeOptional: (ab: Optional) => (sa: Prism) => Optional 379 | ``` 380 | 381 | Added in v2.3.0 382 | 383 | ## composePrism 384 | 385 | Alias of `compose`. 386 | 387 | **Signature** 388 | 389 | ```ts 390 | export declare const composePrism: (ab: Prism) => (sa: Prism) => Prism 391 | ``` 392 | 393 | Added in v2.3.8 394 | 395 | ## composeTraversal 396 | 397 | Compose a `Prism` with an `Traversal`. 398 | 399 | **Signature** 400 | 401 | ```ts 402 | export declare const composeTraversal: (ab: Traversal) => (sa: Prism) => Traversal 403 | ``` 404 | 405 | Added in v2.3.8 406 | 407 | # constructors 408 | 409 | ## fromPredicate 410 | 411 | **Signature** 412 | 413 | ```ts 414 | export declare const fromPredicate: { 415 | (refinement: Refinement): Prism 416 | (predicate: Predicate): Prism 417 | } 418 | ``` 419 | 420 | Added in v2.3.0 421 | 422 | ## id 423 | 424 | **Signature** 425 | 426 | ```ts 427 | export declare const id: () => Prism 428 | ``` 429 | 430 | Added in v2.3.0 431 | 432 | ## prism 433 | 434 | **Signature** 435 | 436 | ```ts 437 | export declare const prism: (getOption: (s: S) => O.Option, reverseGet: (a: A) => S) => Prism 438 | ``` 439 | 440 | Added in v2.3.8 441 | 442 | # converters 443 | 444 | ## asOptional 445 | 446 | View a `Prism` as a `Optional`. 447 | 448 | **Signature** 449 | 450 | ```ts 451 | export declare const asOptional: (sa: Prism) => Optional 452 | ``` 453 | 454 | Added in v2.3.0 455 | 456 | ## asTraversal 457 | 458 | View a `Prism` as a `Traversal`. 459 | 460 | **Signature** 461 | 462 | ```ts 463 | export declare const asTraversal: (sa: Prism) => Traversal 464 | ``` 465 | 466 | Added in v2.3.0 467 | 468 | # instances 469 | 470 | ## Category 471 | 472 | **Signature** 473 | 474 | ```ts 475 | export declare const Category: Category2<'monocle-ts/Prism'> 476 | ``` 477 | 478 | Added in v2.3.0 479 | 480 | ## Invariant 481 | 482 | **Signature** 483 | 484 | ```ts 485 | export declare const Invariant: Invariant2<'monocle-ts/Prism'> 486 | ``` 487 | 488 | Added in v2.3.0 489 | 490 | ## Semigroupoid 491 | 492 | **Signature** 493 | 494 | ```ts 495 | export declare const Semigroupoid: Semigroupoid2<'monocle-ts/Prism'> 496 | ``` 497 | 498 | Added in v2.3.8 499 | 500 | ## URI 501 | 502 | **Signature** 503 | 504 | ```ts 505 | export declare const URI: 'monocle-ts/Prism' 506 | ``` 507 | 508 | Added in v2.3.0 509 | 510 | ## URI (type alias) 511 | 512 | **Signature** 513 | 514 | ```ts 515 | export type URI = typeof URI 516 | ``` 517 | 518 | Added in v2.3.0 519 | 520 | # model 521 | 522 | ## Prism (interface) 523 | 524 | **Signature** 525 | 526 | ```ts 527 | export interface Prism { 528 | readonly getOption: (s: S) => Option 529 | readonly reverseGet: (a: A) => S 530 | } 531 | ``` 532 | 533 | Added in v2.3.0 534 | --------------------------------------------------------------------------------