{"id":41979602,"url":"https://github.com/berberman/nix-optics","last_synced_at":"2026-01-25T23:39:32.808Z","repository":{"id":333599040,"uuid":"1137428454","full_name":"berberman/nix-optics","owner":"berberman","description":"Naive implementation of Profunctor Optics in Nix","archived":false,"fork":false,"pushed_at":"2026-01-20T08:42:14.000Z","size":25,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-01-20T09:26:48.298Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Nix","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/berberman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-19T11:03:03.000Z","updated_at":"2026-01-20T08:42:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/berberman/nix-optics","commit_stats":null,"previous_names":["berberman/nix-optics"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/berberman/nix-optics","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berberman%2Fnix-optics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berberman%2Fnix-optics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berberman%2Fnix-optics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berberman%2Fnix-optics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/berberman","download_url":"https://codeload.github.com/berberman/nix-optics/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berberman%2Fnix-optics/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28761815,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T23:06:19.311Z","status":"ssl_error","status_checked_at":"2026-01-25T23:03:50.555Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":"2026-01-25T23:39:32.156Z","updated_at":"2026-01-25T23:39:32.803Z","avatar_url":"https://github.com/berberman.png","language":"Nix","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nix-optics\n\nNaive implementation of Profunctor Optics in Nix.\n\nSome good resources to understand optics:\n- [Don't Fear The Profunctor Optics](https://github.com/hablapps/DontFearTheProfunctorOptics)\n- [Profunctor Optics: Modular Data Accessors](https://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/poptics.pdf)\n\nThe foundation of this library is almost entirely based on the above two resources, with some inspiration from Haskell's [lens](https://hackage.haskell.org/package/lens) and [microlens](https://hackage-content.haskell.org/package/microlens) libraries.\n\n## Structure\n\n- `default.nix`: Entry point that exposes the optics library. Also includes accessories like `view`, `over`, `set`, etc.\n- `classes.nix`: Class dictionaries for Profunctors, Strong, Choice, etc.\n- `optics.nix`: Implementation of Lenses, Prisms, Affines, Isos, and Traversals. Also includes common optics like `_Just`, `ix`, `at`, etc.\n- `example.nix`: Some LLM-generated examples :(\n\nThese files are well-documented, so please refer to the comments in the code for type signatures and details.\n\n## Example\n\n```nix\n{ nix-optics, ... }:\nlet\n  inherit (nix-optics.lib)\n    view\n    set\n    over\n    toListOf\n    attr\n    ix\n    each\n    compose\n    _Just\n    non\n    json\n    filtered\n    ;\n\n  # A complex nested data structure\n  data = {\n    users = [\n      {\n        id = 1;\n        name = \"Alice\";\n        preferences = {\n          theme = \"dark\";\n          notifications = true;\n        };\n        # Metadata stored as a JSON string\n        meta = \"{\\\"login_count\\\": 42}\";\n      }\n      {\n        id = 2;\n        name = \"Bob\";\n        preferences = null; # Missing\n        meta = \"{\\\"login_count\\\": 0}\";\n      }\n    ];\n  };\n\n  # Optics\n  users = attr \"users\";\n  metadata = compose [\n    (attr \"meta\")\n    json # Automatically converts between JSON and AttrSet\n  ];\n  loginCount = compose [\n    metadata\n    (attr \"login_count\")\n  ];\n  preferences = compose [\n    (attr \"preferences\")\n    _Just # Ignore if null\n  ];\n\nin\n{\n\n  # Get Alice's theme\n  aliceTheme = view (compose [\n    users\n    (ix 0)\n    preferences\n    (attr \"theme\")\n  ]) data;\n  # =\u003e \"dark\"\n\n  # Increment login count for every user\n  updatedLogins = over (compose [\n    users\n    each\n    loginCount\n  ]) (n: n + 1) data;\n  # =\u003e Alice's meta becomes \"{\\\"login_count\\\": 43}\"\n\n  # Set Bob's theme to \"light\", initializing preferences if missing\n  fixBob = set (compose [\n    users\n    (ix 1)\n    (attr \"preferences\")\n    (non { }) # Replaces null with empty set before we write to it\n    (attr \"theme\")\n  ]) \"light\" data;\n  # =\u003e Bob.preferences becomes { theme = \"light\"; }\n\n  # Get a list of all user names\n  allNames = toListOf (compose [\n    users\n    each\n    (attr \"name\")\n  ]) data;\n  # =\u003e [ \"Alice\" \"Bob\" ]\n\n  # Get names of users who have logged in at least once\n  activeUsers = toListOf (compose [\n    users\n    each\n    (filtered (u: (view loginCount u) \u003e 0))\n    (attr \"name\")\n  ]) data;\n  # =\u003e [ \"Alice\" ]\n}\n\n\n```\n\n## Documentation\n\nTypes of Optics:\n\n```haskell\nOptics p s t a b = p a b -\u003e p s t\nLens s t a b = forall p. Strong p =\u003e Optics p s t a b\nPrism s t a b = forall p. Choice p =\u003e Optics p s t a b\nAffine s t a b = forall p. (Strong p, Choice p) =\u003e Optics p s t a b\nIso s t a b = forall p. Profunctor p =\u003e Optics p s t a b\nTraversal s t a b = forall p. Traversing p =\u003e Optics p s t a b\n```\n\nThe type constraints on `p` indicate the capabilities required from the profunctor to construct that optic. The hierarchy of optics is as follows:\n\n```\nIso (Profunctor)                    \n      │                             \n      │                             \n      ├─────   Lens (Strong)   ───┐ \n      │                           │ \n      │                           │ \n      └─────   Prism (Choice)  ───┤ \n                                  │ \n                       ┌──────────┘           \n            Affine (Strong + Choice)\n                       │                  \n                   Traversal        \n```\n\nIt flows from the most capable `Iso` to least capable `Traversal`. Each optic can be used wherever a less capable optic is required. The resulting optic of composing two optics takes the intersection of their capabilities. For example:\n\n- `Lens` + `Prism` = `Affine`\n- `Lens` + `Iso` = `Lens`\n- `Prism` + `Affine` = `Prism`\n- `Traversal` + `Lens` = `Traversal`\n\n### Operators\n\nLet's roughly denote an Optic as `Optic s t a b` in this section for simplicity. The compatible optics for each operator are mentioned, and we'll see different constructions in the next section.\n\n- `view :: Optic s t a b -\u003e s -\u003e a?`: Extracts the first focus of the optic from the structure `s`. Returns `null` if the focus is not present. Works with all optics.\n\n  ```nix\n  view (attr \"x\") { x = 10; y = 20; }\n  # =\u003e 10\n  ```\n\n- `over :: Optic s t a b -\u003e (a -\u003e b) -\u003e s -\u003e t`: Modifies the focus of the optic using a function. Works with all optics.\n\n  ```nix\n  over (attr \"count\") (x: x + 1) { count =  0; }\n  # =\u003e { count = 1; }\n  ```\n\n- `set :: Optic s t a b -\u003e b -\u003e s -\u003e t`: Replaces the focus of the optic with a new value. Works with all optics.\n\n  ```nix\n  set (attr \"valid\") true { valid = false; }\n  # =\u003e { valid = true; }\n  ```\n\n- `toListOf :: Optic s t a b -\u003e s -\u003e [a]`: Extracts all targets of an optic into a list. Useful with `each`. Works with all optics.\n\n  ```nix\n  toListOf each { a = 1; b = 2; }\n  # =\u003e [ 1 2 ]\n\n  toListOf (compose [ each (attr \"x\") ]) [ { x = 1; } { x = 2; } ]\n  # =\u003e [ 1 2 ]\n  ```\n\n- `match/preview :: Optic s t a b -\u003e s -\u003e Either a t`: Attempts to match a `Prism` or `Affine`. Returns `{ left = value; }` on success or `{ right = newStructure; }` on failure. Works with `Prism` and `Affine` optics. (It won't fail on others but not very useful.)\n\n  ```nix\n  match _Just 5\n  # =\u003e { left = 5; }\n\n  match _Just null\n  # =\u003e { right = null; }\n  ```\n\n- `build`: Constructs a structure from a value. Works with `Iso` and `Prism` optics. Not very useful so far.\n\n  ```nix\n  build _Just 10\n  # =\u003e 10\n\n  build json { a = 1; }\n  # =\u003e \"{ \\\"a\\\": 1 }\"\n  ```\n\n  \u003e Note: `build (compose [ _Just (attr \"x\") ]) 10` would fail because the resulting optic is an `Affine`, not a `Prism`.\n\n- Folds (`sumOf`, `productOf`, `anyOf`, `allOf`, `lengthOf`): Aggregate values focused. Works with `Traversals` (`each`).\n  ```nix\n  anyOf each [ false true ]\n  # =\u003e true\n\n  sumOf (compose [ each (attr \"x\") ]) [ { x = 1; } { x = 2; } { x = 3; } ]\n  # =\u003e 6\n  ```\n\n### Optic Constructors\n\n- `lens :: (s -\u003e a) -\u003e (s -\u003e b -\u003e t) -\u003e Lens s t a b`: Constructs a Lens from a getter and a setter.\n  ```nix\n  let first = lens (p : p.fst) (p: v: p // { fst = v; }); \n      pair = { fst = 1; snd = 2; };\n  in\n    view first pair\n    # =\u003e 1\n  \n    set first 10 pair\n    # =\u003e { fst = 10; snd = 2; }\n  ```\n\n- `prism :: (s -\u003e Either a t) -\u003e (b -\u003e t) -\u003e Prism s t a b`: Constructs a Prism from a matcher and a builder. See `_Just` for an example.\n\n- `affine :: (s -\u003e Either a t) -\u003e (s -\u003e b -\u003e t) -\u003e Affine s t a b`: Constructs an Affine from a matcher and a setter. See `ix` for an example.\n\n- `iso :: (s -\u003e a) -\u003e (b -\u003e t) -\u003e Iso s t a b`: Constructs an Iso from a pair of functions. See `json` for an example.\n\n### Some Common Optics\n\n- `compose :: [Optic] -\u003e Optic`: Optics can be composed as functions naturally. `compose` just composes a list of optics into one.\n  ```nix\n  view (x: (at \"a\") ((at \"b\") x)) { a.b = 1; }\n  # =\u003e 1\n  view (compose [ (attr \"a\") (attr \"b\") ]) { a.b = 1; }\n  # =\u003e 1\n  ```\n\n- `attr :: String -\u003e Lens AttrSet AttrSet a b`: Lens focusing on an attribute of an attribute set. Fails if the attribute does not exist.\n  ```nix\n  set (attr \"foo\") 42 { foo = 0; bar = 1; }\n  # =\u003e { foo = 42; bar = 1; }\n  ```\n\n- `path :: [String] -\u003e Lens AttrSet AttrSet a b` : A list of `attr`s composed together.\n  ```nix\n  set (path [ \"a\" \"b\" \"c\" ]) 233 { a.b = {c = 0; d = 1; }; }\n  # =\u003e { a.b = { c = 233; d = 1; }; }\n  ```\n\n- `attr' :: String -\u003e Affine AttrSet AttrSet a b`: Affine focusing on an attribute of an attribute set. Does nothing if the attribute does not exist. Note: it never creates new attributes.\n  ```nix\n  set (attr' \"a\") 2 { a = 1; }\n  # =\u003e { a = 2; }\n\n  set (attr' \"b\") 2 { a = 1; }\n  # =\u003e { a = 1; }\n  ```\n- `at :: String -\u003e Lens AttrSet AttrSet a? b?`: Lens focusing on a nullable attribute. Allows deleting by setting it to `null`.\n  ```nix\n  set (at \"a\") null { a = 1; b = 2; }\n  # =\u003e { b = 2; }\n\n  set (at \"c\") 3 { a = 1; b = 2; }\n  # =\u003e { a = 1; b = 2; c = 3; }\n  ```\n\n- `ix :: Int -\u003e Affine [a] [b] a b`: Affine focusing on a specific index of a List. Does nothing if the index is out of bounds.\n  ```nix\n  set (ix 1) 233 [ 1 2 3 ]\n  # =\u003e [ 1 233 3 ]\n\n  set (ix 233) 99 [ 1 2 3 ]\n  # =\u003e [ 1 2 3 ]\n  ```\n- `each :: Traversal (c a) (c b) a b` where `c` is either List or AttrSet: Traversal over all elements of a List or AttrSet.\n  ```nix\n  over each (x: x * 2) [ 1 2 3 ]\n  # =\u003e [ 2 4 6 ]\n\n  over each (x: x + 1) { a = 1; b = 2; }\n  # =\u003e { a = 2; b = 3; }\n  ```\n\n- `filtered :: (a -\u003e Bool) -\u003e Prism a a a a`: Prism focusing on values that satisfy a predicate.\n  ```nix\n  view (filtered (x: x \u003e 0)) 10\n  # =\u003e 10\n\n  # Increment only even numbers\n  over (compose [ each (filtered (x: x / 2 * 2 == x)) ]) (x: x + 1) [ 1 2 3 4 ]\n  # =\u003e [ 1 3 3 5 ]\n  ```\n\n- `_Just :: Prism a? b? a b`: Prism focusing on the value if it's not null.\n  ```nix\n  toListOf (compose [ each _Just ]) [ 1 null 2 null 3 ]\n  # =\u003e [ 1 2 3 ]\n  ```\n\n- `non :: a -\u003e Iso a? a? a a`: An Iso that substitutes `null` with a default value.\n  ```nix\n  toListOf (compose [ each (non 0) ]) [ 1 null 2 null 3 ]\n  # =\u003e [ 1 0 2 0 3 ]\n\n  view (compose [ (at \"missing\") (non 0) ]) { }\n  # =\u003e 0\n  ```\n\n- `json :: Iso String String a a`: An Iso that converts between JSON strings and Nix values.\n  ```nix\n  view json \"{\\\"a\\\": 1}\"\n  # =\u003e { a = 1; }\n\n  over json (x: x // { a = 1; }) \"{\\\"b\\\": 2}\"\n  \"{\\\"a\\\":1,\\\"b\\\":2}\"\n  ```\n\n## TODO\n\n- Properly implement Traversals: now they are specialized to Lists only and very ad-hoc, though I'm not sure if it's a good idea to introduce a separate `Monoidal` class, or even representable functors...\n- Add more useful optics and combinators for common use cases in Nix.\n- Write tests and examples.\n\n## Contributing\n\nIssues and PRs are always welcome. **\\_(:з」∠)\\_**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberberman%2Fnix-optics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fberberman%2Fnix-optics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberberman%2Fnix-optics/lists"}