{"id":28509165,"url":"https://github.com/atomicobject/lenses","last_synced_at":"2025-06-30T02:35:44.084Z","repository":{"id":46938737,"uuid":"92550554","full_name":"atomicobject/lenses","owner":"atomicobject","description":null,"archived":false,"fork":false,"pushed_at":"2023-01-12T09:52:33.000Z","size":155,"stargazers_count":56,"open_issues_count":12,"forks_count":6,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-06-08T22:08:11.865Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atomicobject.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-26T21:44:21.000Z","updated_at":"2024-11-27T15:28:01.000Z","dependencies_parsed_at":"2023-02-09T11:15:48.619Z","dependency_job_id":null,"html_url":"https://github.com/atomicobject/lenses","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/atomicobject/lenses","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Flenses","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Flenses/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Flenses/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Flenses/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atomicobject","download_url":"https://codeload.github.com/atomicobject/lenses/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Flenses/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262699455,"owners_count":23350298,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-08T22:07:39.375Z","updated_at":"2025-06-30T02:35:44.076Z","avatar_url":"https://github.com/atomicobject.png","language":"TypeScript","readme":"# Lenses\n\n`@atomic-object/lenses` is a small functional lens library for TypeScript with the goal of being small, with zero dependencies, and strong, precise types. It is inspired by [Aether](https://github.com/xyncro/aether), for F#.\n\nLenses are getter/setter pairs that let you represent a location within some data structure for both reading and updating that location. \"Update\" in this case is in the functional sense – not by mutation, but by creating a new data structure with a new value in the location of interest. A little like a pointer offset in C, but memory-, type-, and mutation-safe.\n\n\n## Basic Usage\n\nThe simplest use is to represent a property of an object. Consider this type \n\n```ts\ntype Something = { foo: number; bar: string };\n```\n\nWe could define a helper module with lenses for interacting with this type:\n\n```ts\nexport namespace Something = {\n  export const foo = Lens.from\u003cSomething\u003e().prop(\"foo\");\n  export const bar = Lens.from\u003cSomething\u003e().prop(\"bar\");\n}\n```\n\nIn this example, `Something.foo` has type `Lens\u003cSomething, number\u003e`, meaning that it can read numbers from and write numbers to a `Something`. `bar` is inferred to have type `Lens\u003cSomething, string\u003e` – it only accepts string values.\n\nGiven a value of type `Something`:\n\n```ts\nlet o: Something = { foo: 1, bar: \"hello\" };\n```\n\nwe can get values\n\n```ts\n// Get the foo of a Something\nexpect(Something.foo.get(o)).toBe(1);\n// Or just treat the lens as a function to do the same thing:\nexpect(Something.foo(o)).toBe(1);\nexpect(Something.bar(o)).toEqual(\"hello\")\n```\n\nAnd we can create updated by `set`ting the lens:\n\n```ts\nlet o2 = Something.foo.set(o, 10);\nexpect(o2).toEqual({ foo: 10, bar: \"hello\" });\nexpect(o).toEqual({ foo: 1, bar: \"hello\" });\n```\n\nOr `update`-ing the value by running it through a function:\n\n```ts\nlet o3 = Something.foo.update(o, i =\u003e i+1);\nexpect(o3).toEqual({ foo: 2, bar: 'hello'})\n```\n\n## Lens Composition\n\nLenses can also be composed. This is a powerful technique for building abstractions. While immutability helper and spreads require deep knowledge about the shape of a data structure, violating the [Law of Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter), lenses represent concepts, and programming to them decouples you from the underlying data structure.\n\nFor example, consider a container type which contains a `Something` and a value of that type:\n\n```ts\ntype ContainsSomething = {\n  something: Something;\n};\nconst container: ContainsSomething = {\n  something: { foo: 19, bar: \"hola\" }\n};\n```\n\nWe can create a lens for the `something` property, and compose it with our other lenses:\n\n```ts\nlet innerFoo = Lens.from\u003cContainsSomething\u003e()\n  .prop(\"something\")\n  .comp(Something.foo);\nexpect(innerFoo(container)).toEqual(19);\n```\n\nUsers of our `innerFoo` lens don't need to couple themselves to either the location of `Something` within `ContainsSomething`, nor the location of the logical value of `foo` within it. We're completely free to reorganize our data structure, provided all users of it are programmed to lenses.\n\n## Currying\n\nBoth `set` and `update` are curried – you can provide just a target value or an update function to get back \"updater\" functions (e.g. `Something =\u003e Something`) that can be composed together to make multiple updates at once.\n\nFor example,\n\n```ts\nimport { flow } from \"lodash\";\nlet o5 = flow(\n  Something.foo.update(i =\u003e 10 * i),\n  Something.bar.set(\"world\")\n)(o);\n```\n\n## Custom Lenses\n\nLenses need not point simply to properties of an object, but can be used for anything that could be logically get/set. The underlying representation need not matter.\n\nFor example, we could create a lens that presents the low bit of an integer as a boolean:\n\n```ts\nconst lowBitLens = Lens.of\u003cnumber, boolean\u003e({\n  get: n =\u003e (n \u0026 1 ? true : false),\n  set: (n, b) =\u003e (b ? n | 1 : n \u0026 ~1)\n});\n```\n\nGiven this definition, we're free to read/write booleans into numbers as follows:\n\n```ts\nexpect(lowBitLens(10)).toBe(false);\nexpect(lowBitLens(11)).toBe(true);\nexpect(lowBitLens.set(10, true)).toBe(11);\nexpect(lowBitLens.set(11, false)).toBe(10);\nexpect(lowBitLens.update(9, b =\u003e !b)).toBe(8);\n```\n\n## Isomorphisms\n\nIn addition to creating lenses with `Lens.of` that operate on arbitrary substructure – or even equivalent substructure, such as the low-bit lens example – you can also `map` from one lens type to another.\n\nFor example, let's say you have a menu component that takes a `MenuProps`. You have `ApplicationState` in your redux store that you want to control your menu, but that state may be in charge of other things as well that should all be consistent. If you can provide a bi-directional mapping between your application state and a `MenuProps`, you could always convert your app state into menu inputs, and changes to the menu props back into equivalent changes in your application state. Your menu, therefore, can think it is operating on a `MenuProps` when instead it's updating `ApplicationState`.\n\nFor a simpler example, let's look at an isomorphism that converts numbers to strings, and use it to create a lens that operates on strings, but stores the value as a number.\n\n```ts\nlet n2s: Isomorphism\u003cnumber, string\u003e = {\n  to: n =\u003e n.toString(),\n  from: s =\u003e parseInt(s, 10)\n};\nconst sFoo = Lens.map(Something.foo, n2s);\n\nlet o: Something = { foo: 1, bar: \"hello\" };\nexpect(sFoo(o)).toEqual(\"1\");\nconst o6 = sFoo.set(o, \"1234\");\nexpect(o6).toEqual({ foo: 1234, bar: \"hello\" });\n```\n\n## Prisms\n\nWe also provide a type for Prisms, which are lenses for which `get` may fail, returning `undefined`. See the code/tests for more examples.\n\n\n## Functional array helpers\n\n`@atomicobject/lenses/lib/arrays` provides functional versions of `splice`, `pop`, `push`, `unshift`, and `shift`, as well as an `index` function which returns a `Prism` for read/writing an arbitrary index in an array.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatomicobject%2Flenses","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatomicobject%2Flenses","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatomicobject%2Flenses/lists"}