{"id":24974785,"url":"https://github.com/adzz/keyword_lens","last_synced_at":"2025-08-22T02:08:33.404Z","repository":{"id":55388433,"uuid":"312719921","full_name":"Adzz/keyword_lens","owner":"Adzz","description":"A WIP utility library for working with nested data structures","archived":false,"fork":false,"pushed_at":"2021-07-06T13:57:19.000Z","size":149,"stargazers_count":4,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-21T19:10:32.030Z","etag":null,"topics":["elixir","fp","functional-programming","keyword","lens","lenses","zipper"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Adzz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-14T01:10:34.000Z","updated_at":"2025-01-28T23:01:17.000Z","dependencies_parsed_at":"2022-08-14T23:10:35.667Z","dependency_job_id":null,"html_url":"https://github.com/Adzz/keyword_lens","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Adzz/keyword_lens","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Adzz%2Fkeyword_lens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Adzz%2Fkeyword_lens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Adzz%2Fkeyword_lens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Adzz%2Fkeyword_lens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Adzz","download_url":"https://codeload.github.com/Adzz/keyword_lens/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Adzz%2Fkeyword_lens/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271574431,"owners_count":24783319,"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","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["elixir","fp","functional-programming","keyword","lens","lenses","zipper"],"created_at":"2025-02-03T20:42:43.276Z","updated_at":"2025-08-22T02:08:33.380Z","avatar_url":"https://github.com/Adzz.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# KeywordLens\n\nA keyword lens is a nested keyword-like structure used to describe paths into certain data types. It is similar to the list you can provide to Ecto's Repo.preload/2\n\nYou can describe a KeywordLens like this:\n```elixir\n[a: :b, c: [d: :e]]\n```\n\nSuch a list is handy for describing subsets of nested data structures. For example, you can imagine the following KeywordLens: `[a: :b]` applied to this map: `%{a: %{b: 1}}` points to the value `1`. In contrast this KeywordLens: `[:a, :b]` applied to this map `%{a: 1, b: 2}` points to both values `1` and `2`.\n\nIt's not a proper Keyword list because we allow any key for convenience, so these are valid:\n\n```elixir\n[{\"a\", :b}]\n[a: [{\"b\", [c: :d]}]\n[{%{}, :b}]\n[{1, {2, 3}}]\n```\n\nOne KeywordLens can point to many different values inside a given data structure.\n\nHere are some examples of different KeywordLenses and the unique set of lenses they represent.\n\n```elixir\nkeyword_lens = [a: :b]\nlenses = [[:a, :b]]\n\nkeyword_lens = [:a, :b]\nlenses = [[:a], [:b]]\n\nkeyword_lens = [a: [b: [:c, :d]]]\nlenses = [[:a, :b, :c], [:a, :b, :d]]\n\nkeyword_lens = [a: [:z, b: [:c, d: :e]]]\nlenses = [[:a, :z], [:a, :b, :c], [:a, :b, :d, :e]]\n\nkeyword_lens = [:a, \"b\", :c]\nlenses = [[:a], [\"b\"], [:c]]\n```\n\nYou can use `KeywordLens.Helpers.expand/1` to see which unique lenses are encoded in a given KeywordLens.\n\n```elixir\nKeywordLens.Helpers.expand([a: :b])\n[[:a, :b]]\n\nKeywordLens.Helpers.expand([a: [b: [:c, :d]]])\n[[:a, :b, :c], [:a, :b, :d]]\n```\n\nThis library provides a protocol you can implement for your own data structures and structs. We provide a map implementation to get started.\n\n### Examples\n\n```elixir\nKeywordLens.map(%{a: %{b: 1}}, [a: :b], \u0026(\u00261 + 1))\n%{a: %{b: 2}}\n```\n\n### Can't I just use get_in / update_in\n\nYou could, but the syntax becomes a bit verbose and repetitive:\n\n```elixir\n(\n  %{a: %{b: 1}, c: %{d: 1, e: 1}}\n  |\u003e update_in([:a, :b], \u0026 \u00261 + 1)\n  |\u003e update_in([:c, :d], \u0026 \u00261 + 1)\n  |\u003e update_in([:c, :e], \u0026 \u00261 + 1)\n)\n%{a: %{b: 2}, c: %{d: 2, e: 2}}\n\n# Vs\n\nKeywordLens.map(%{a: %{b: 1}, c: %{d: 1, e: 1}}, [a: :b, c: [:d, :e]], \u0026 \u00261+1)\n%{a: %{b: 2}, c: %{d: 2, e: 2}}\n```\n\nAdditionally `get_in` will return nil if you provide a path that doesn't point to a value:\n\n```elixir\nKernel.get_in(%{}, [:a])\nnil\n```\n\nThis can be fine, but can make it tricky to distinguish between \"the path you gave me doesn't point to a value\" and \"the path you gave me points to a value, and that value is nil\":\n\n```elixir\nKernel.get_in(%{}, [:a])\nnil\n\nKernel.get_in(%{a: nil}, [:a])\nnil\n```\n\nThat might not matter to you. KeywordLens takes the following approach for now:\n\n```elixir\nKeywordLens.map(%{}, [:a], \u0026 \u00261)\n** (KeyError) key :a not found in: %{}\n\nKeywordLens.map(%{a: nil}, [:a], \u0026 \u00261)\n%{a: nil}\n\nKeywordLens.map(%{a: 1}, [a: :b], \u0026 \u00261)\n** (KeywordLens.InvalidPathError) a KeywordLens requires that each key in the path points to a map until the last key in the path. It looks like your path is wrong, please check.\n```\n\nWhat's nice about this is you get a slightly clearer error message than what `get_in` can provide if you use an incorrect path. You can think of `KeywordLens.map` as being a `fetch!_and_update_in`.\n\nFor now, if you want to use the compact KeywordLens notation, but have the semantics of `get_and_update_in` you can do this:\n\n```elixir\n(\n  data = %{a: %{b: 1}, c: %{d: 1, e: 1}}\n  KeywordLens.Helpers.expand([a: :b, c: [:d, :e]])\n  |\u003e Enum.reduce(data, fn path, acc -\u003e\n    {_, result} = get_and_update_in(acc, path, \u0026({\u00261, \u00261 + 1}))\n    result\n  end)\n)\n```\n\n### Aside what is a zipper?\n\nIt's a way of traversing a structure without losing the parts you have visited, meaning you can step back or forwards through the traversal trivially. Let's take a list as an example\n```\n[1, 2, 3, 4, 5]\n```\nAs we step through this we could break it into two halves, one side would have the nodes we haven't seen the other the ones we have:\n```\nunseen = [2, 3, 4, 5]; seen = [1]\n```\nStepping forward is about taking the head of unseen and putting it on the head of seen:\n```\nunseen = [3, 4, 5]; seen = [2, 1]\nunseen = [4, 5]; seen = [3, 2, 1]\n```\nStepping backwards is the reverse:\n```\nunseen = [2, 3, 4, 5]; seen = [1]\nunseen = [1, 2, 3, 4, 5]; seen = []\n```\n\nInternally we iterate through the nested data structures in this way.\n\nTODO: expand this explanation.\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed\nby adding `keyword_lens` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:keyword_lens, \"~\u003e 0.1.1\"}\n  ]\nend\n```\n\nDocumentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)\nand published on [HexDocs](https://hexdocs.pm). Once published, the docs can\nbe found at [https://hexdocs.pm/keyword_lens](https://hexdocs.pm/keyword_lens).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadzz%2Fkeyword_lens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadzz%2Fkeyword_lens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadzz%2Fkeyword_lens/lists"}