{"id":26364773,"url":"https://github.com/lightandlight/indexed-paths","last_synced_at":"2026-04-08T14:03:19.557Z","repository":{"id":78647571,"uuid":"357741538","full_name":"LightAndLight/indexed-paths","owner":"LightAndLight","description":"A library for working with free categories","archived":false,"fork":false,"pushed_at":"2021-04-18T02:28:47.000Z","size":59,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-12T10:46:28.274Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","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/LightAndLight.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,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-04-14T01:52:30.000Z","updated_at":"2021-06-04T00:03:57.000Z","dependencies_parsed_at":"2023-05-06T16:46:45.780Z","dependency_job_id":null,"html_url":"https://github.com/LightAndLight/indexed-paths","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LightAndLight%2Findexed-paths","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LightAndLight%2Findexed-paths/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LightAndLight%2Findexed-paths/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LightAndLight%2Findexed-paths/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LightAndLight","download_url":"https://codeload.github.com/LightAndLight/indexed-paths/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243919135,"owners_count":20368837,"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-03-16T19:28:18.503Z","updated_at":"2026-04-08T14:03:14.514Z","avatar_url":"https://github.com/LightAndLight.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `indexed-paths`\n\nA library for working with 'free categories'. Docs are [here](https://blog.ielliott.io/indexed-paths).\n\n## Contents\n\n* Explanation\n* Applications\n    * State machines\n    * Lensing\n* How have I used it?\n* Prior Art\n\n## Explanation\n\nWe're going to build some graphs. Suppose `k` is the kind of vertices in your\ngraph. If you you define a type `G :: k -\u003e k -\u003e Type`, then each value of type\n`G a b` can be seen as an edge from `a :: k` to `b :: k`.\n\nExample:\n\n```haskell\n{-\n\nG1:\n\nA ---W--\u003e B\n^         |\n|         |\nZ         X\n|         |\n|         v\nD \u003c--Y--- C\n\n-}\n\ndata Node = A | B | C | D\n\ndata G1 :: Node -\u003e Node -\u003e Type where\n  W :: G1 A B\n  X :: G1 B C\n  Y :: G1 C D\n  Z :: G1 D A\n```\n\nThe graphs we're defining are directed graphs that can have\nmore than one edge between any two vertices. These are known as\n[quivers](https://en.wikipedia.org/wiki/Quiver_(mathematics)).\n\nEvery quiver defined in this manner gives rise to a category.\nThe objects of the category are vertices in the graph, and the\narrows of the category are sequences of graph edges, where the\ntarget vertex of an edge matches the source node of the subsequent\nedge in the sequence. Put simply, the arrows of the category are all\npaths you can take by following edges of the graph. For each quiver `G`,\nwe'll call the category generated in this manner `Path(G)`.\n\nHere's what `Path` looks like in Haskell:\n\n```\ndata Path (g :: k -\u003e k -\u003e Type) :: k -\u003e k -\u003e Type where\n  Nil :: Path g a a\n  Cons :: g a b -\u003e Path g b c -\u003e Path g a c\n```\n\nIt's literally a linked list of adjacent edges in the graph.\n\nWhen it comes to representing sequences in programming, a linked list\nis not always the best choice. There are a variety of sequence types with\noperations that differ in space and time usage. `Path`s, being fundamentally\nsequences, are also subject to the same considerations. This library defines\na few different representations and a type class for common operations, which \nallows efficient use of `Path`s in various circumstances.\n\n## Applications\n\n### State machines\n\nWe can model state machines as quivers.\n\nExample:\n\nA door that can only be opened when it is unlocked,\nand can only be locked when it is closed.\n\n```haskell\n{-\n\nLocked ---Unlock--\u003e Closed ---Open--\u003e Opened\n  ^                 |   ^                |\n  |                 |   |                |\n  +-------Lock------+   +-----Close------+\n\n-}\n\ndata DoorS = Locked | Closed | Opened\n\ndata DoorG :: DoorS -\u003e DoorS -\u003e Type where\n  Unlock :: DoorG Locked Closed\n  Lock :: DoorG Closed Locked\n  Open :: DoorG Closed Opened\n  Close :: DoorG Opened Closed\n```\n\n`Path DoorG a b` represents a sequence of door actions that\nstart in state `a` and end in state `b`. We can then define the\ndoor's runtime state, the action of an edge on that state,\nand then use a sequence of door actions to affect that state.\n\n```\ndata SDoorS :: DoorS -\u003e Type where\n  SLocked :: SDoorS Locked\n  SClosed :: SDoorS Closed\n  SOpened :: SDoorS Opened\n  \ndata Door :: DoorS -\u003e Type where\n  Door :: { state :: SDoorS s, locks :: Int, unlocks :: Int, opens :: Int, closes :: Int } -\u003e Door s\n  \nact :: DoorG a b -\u003e Door a -\u003e Door b\nact edge door =\n  case edge of\n    Unlock -\u003e\n      door { state = SUnlocked, unlocks = unlocks door + 1 }\n    Lock -\u003e\n      door { state = SLocked, locks = locks door + 1 }\n    Open -\u003e\n      door { state = SOpened, opens = opens door + 1 }\n    Close -\u003e\n      door { state = SClosed, closes = closes door + 1 }\n      \nactMany :: Path DoorG a b -\u003e Door a -\u003e Door b\nactMany = composeMap act\n```\n\n### Lensing\n\nQuivers of kind `Type -\u003e Type -\u003e Type` (quivers where the vertices are Haskell types),\ncan be used to describe the structure of datatypes.\n\nExamples:\n\n```haskell\ndata Pair a b = Pair a b\n\n{-\nPair a b gives rise to the graph\n\n    Pair a b\n       / \\\n     Fst  Snd\n     /     \\\n    v       v\n    a       b\n\nwhich can be encoded as the graph\n-}\n\ndata PairG :: * -\u003e * -\u003e * where\n  Fst :: PairG (Pair a b) a\n  Snd :: PairG (Pair a b) b\n```\n\n```haskell\ndata Sum a b = Left a | Right b\n\n{-\n\n    Sum a b\n       / \\\n      L   R\n     /     \\\n    v       v\n    a       b\n\n-}\n\ndata SumG :: * -\u003e * -\u003e * where\n  L :: SumG (Either a b) a\n  R :: SumG (Either a b) b\n```\n\n```haskell\ndata List a = Nil | Cons a (List a)\n\n{-\n\n      List a \u003c-+\n      /  \\     |\n    Head  Tail-+\n     |     \n     v       \n     a       \n\n-}\n\ndata ListG :: * -\u003e * -\u003e * where\n  Head :: ListG (List a) a\n  Tail :: ListG (List a) (List a)\n```\n\nWhen a datatype-graph contains loops, such as `List`, `Path` can be used\nto name items that are deeply nested in the datatype.\n\n```haskell\n{-\n\nCons 1 (Cons 2 (Cons 3 Nil))\n         p1: ^\n-}\n\np1 = Path.cons Tail (Path.singleton Head)\n\n\n{-\n\nCons 1 (Cons 2 (Cons 3 Nil))\n                 p2: ^\n-}\n\np2 = Path.cons Tail (Path.cons Tail (Path.singleton Head))\n```\n\nThis is very similar to the idea of a \n[`Traversal'`](https://hackage.haskell.org/package/lens-5.0.1/docs/Control-Lens-Traversal.html#t:Traversal-39-),\nand this package provides a way to map paths into `Traversal'`s.\n\n```haskell\ninstance ToTraversal ListG where\n  toTraversal g =\n    case g of\n      Head -\u003e\n        \\f l -\u003e case l of\n          Nil -\u003e\n            pure l\n          Cons a b -\u003e\n            (\\a' -\u003e Cons a' b) \u003c$\u003e f a\n      Tail -\u003e\n        \\f l -\u003e case l of\n          Nil -\u003e\n            pure l\n          Cons a b -\u003e\n            Cons a \u003c$\u003e f b\n\nlist = Cons 1 (Cons 2 (Cons 3 Nil))\n\n{-\n\n\u003e list ^? pathed (Path.cons Tail (Path.cons Tail (Path.singleton Head)))\nJust 3\n\n\u003e list ^? pathed (Path.cons Tail (Path.cons Tail (Path.cons Tail (Path.singleton Head))))\nNothing\n\n\u003e list ^? pathed (Path.singleton Head)\nJust 1\n\n-}\n```\n\n## How have I used it?\n\nI came across these ideas while developing structured code editors. Editor state commonly\nincludes a syntax tree paired with a 'path', which 'points' to the currently focused node.\nEdit actions then use the path to traverse the syntax tree and apply the relevant update to\nthe focused node. `Trie`s map paths to nodes, and can be used to efficiently annotate the \nsyntax tree with warnings and errors.\n\nI started using a free-category-based path for better type safety, which meant less 'runtime\ntype checking' and simpler editing code. Then when I realised that paths could be mapped to\n`Traversal'`, I was able to delete most of the code required to edit the syntax tree and re-use \n`lens` instead.\n\n## Prior Art\n\nA Google search for \"free category hackage\" gives me these two packages:\n\n* https://hackage.haskell.org/package/free-categories\n* https://hackage.haskell.org/package/free-category\n\nThe former has an extensive treatment of quivers, but I don't know how to apply any of the\nconstructions.\n\nThe latter briefly explores the idea of more efficient representations for free categories.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightandlight%2Findexed-paths","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flightandlight%2Findexed-paths","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightandlight%2Findexed-paths/lists"}