{"id":20776417,"url":"https://github.com/aroemers/rmap","last_synced_at":"2026-03-07T09:03:50.685Z","repository":{"id":21419126,"uuid":"24737215","full_name":"aroemers/rmap","owner":"aroemers","description":"Clojure library for defining recursive maps; literally, programmatically and with pure data.","archived":false,"fork":false,"pushed_at":"2024-05-10T20:58:44.000Z","size":219,"stargazers_count":79,"open_issues_count":1,"forks_count":3,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-08T23:44:00.600Z","etag":null,"topics":["clojure","map","recursive","recursive-maps"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aroemers.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2014-10-02T21:23:28.000Z","updated_at":"2025-04-15T06:37:42.000Z","dependencies_parsed_at":"2025-01-03T12:10:40.502Z","dependency_job_id":"9741143e-a9fe-4fc1-8ff1-ad448c3f4dc8","html_url":"https://github.com/aroemers/rmap","commit_stats":{"total_commits":67,"total_committers":5,"mean_commits":13.4,"dds":0.4328358208955224,"last_synced_commit":"f2c8924ebfd96bebdfd51dcfe6f8e71ae17e03fe"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aroemers%2Frmap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aroemers%2Frmap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aroemers%2Frmap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aroemers%2Frmap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aroemers","download_url":"https://codeload.github.com/aroemers/rmap/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253166474,"owners_count":21864467,"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":["clojure","map","recursive","recursive-maps"],"created_at":"2024-11-17T13:08:07.497Z","updated_at":"2026-03-07T09:03:45.601Z","avatar_url":"https://github.com/aroemers.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Clojars Project](https://img.shields.io/clojars/v/functionalbytes/rmap.svg)](https://clojars.org/functionalbytes/rmap)\n[![cljdoc badge](https://cljdoc.org/badge/functionalbytes/rmap)](https://cljdoc.org/d/functionalbytes/rmap/CURRENT)\n[![Clojure CI](https://github.com/aroemers/rmap/workflows/Clojure%20CI/badge.svg?branch=master)](https://github.com/aroemers/rmap/actions?query=workflow%3A%22Clojure+CI%22)\n[![Clojars Project](https://img.shields.io/clojars/dt/functionalbytes/rmap?color=blue)](https://clojars.org/functionalbytes/rmap)\n[![Blogpost](https://img.shields.io/badge/blog-Introducing%20rmap%202.0-blue)](https://functionalbytes.nl/clojure/rmap/2020/06/04/rmap-2.html)\n\n# ➰ rmap\n\nA Clojure library for recursive maps.\n\n![Banner](banner.png)\n\n## An appetizer\n\nThis library allows you to create a recursive maps, i.e. maps with entries that can point to other entries in the same map.\nSee for yourself!\n\n```clj\n;; They can be created literally:\n\n(rmap! {:foo 1\n        :bar (ref :foo)})\n\n;; Or, using plain data:\n\n(rmap! {:foo 1\n        :bar #rmap/ref :foo})\n\n;; Or, programmatically:\n\n(valuate! {:foo 1\n           :bar (rval (ref :foo))})\n```\n\nIn all cases, the result is the same:\n\n```clj\n{:foo 1, :bar 1}\n```\n\nRead on to see how this all works.\n\n## Detailed usage\n\n### The RVal object\n\nWe start with a basic building block: a recursive value.\nA recursive value is an unevaluated expression, which has access to the associative datastructure - i.e. a map or a vector - it will be evaluated in.\nThe expression can access other entries in this datastructure using the `ref` function.\n\nA recursive value is represented in the form of an RVal object.\nYou can create an RVal using the `rval` macro.\nIt simply takes one or more expressions as its body.\nLet's create a simple Clojure map with an RVal object in it and print it:\n\n```clj\n(def my-map\n  {:foo 1\n   :bar (rval (inc (ref :foo)))})\n\nmy-map\n;=\u003e {:foo 1, :bar ??}\n```\n\nAs you can see, the `:bar` entry is an RVal and uses the `ref` function to fetch the value mapped to `:foo`.\nYou can also see that no evaluation has taken place.\n\nThere is a complementary macro, called `rmap`.\nIt lets you create a datastructure from a literal representation, where all values are automatically RVal objects.\nFor example, the following creates a similar map, except that the `:foo` value is now also an RVal:\n\n```clj\n(def my-map\n  (rmap {:foo 1\n         :bar (inc (ref :foo))}))\n\nmy-map\n;=\u003e {:foo ??, :bar ??}\n```\n\nThe `ref` function is only bound during the evaluation of the recursive value, as that is the only moment it makes sense to use it.\nIf you want to use it at a later point, you should bind it locally.\nFor example when using a delay:\n\n```clj\n(rmap {:foo 1\n       :bar (inc (ref :foo))\n       :baz (let [my-ref ref]\n              (delay (my-ref :bar)))})\n```\n\n### Valuating\n\nTo evaluate one or more RVal objects in a particular context, you can use the `valuate!` function.\nIt takes an associative datastructure and returns an updated version of it, where all RVal objects are evaluated.\nA similar function is `valuate-keys!`.\nIt does the same, but only evaluates the specified keys (or indices) and their dependencies.\n\nLet's evaluate the map we created earlier:\n\n```clj\n(valuate! my-map)\n;=\u003e {:foo 1, :bar 2}\n\nmy-map\n;=\u003e {:foo ??, :bar ??}\n\n(valuate-keys! my-map :foo)\n;=\u003e {:foo 1, :bar ??}\n\n(valuate! (assoc my-map :foo 1001))\n;=\u003e {:foo 1001, :bar 1002}\n```\n\nYou can see that the entries are evaluated now, yielding the expected results.\nEach entry is only evaluated once, even if an entry is requested multiple times by other entries.\nAlso note that the original map itself has not changed and can be modified, yielding different results.\n\n### Plain data recursive maps\n\nThere are three slightly more advanced features related to valuating.\n\nFirstly, when an RVal is evaluated, the library post-processes the result by walking through it.\nWhenever a `#rmap/ref \u003ckey\u003e` (a tagged litteral, handled by `rmap.core/ref-tag`) is encountered during this walk, it will be replaced with the value under the referenced key.\nThis way you can create recursive maps using plain data, by reading from an EDN file for example.\n\nSecondly, to create a recursive map from an already existing map, you can also use `rmap`.\nThe resulting map has all values wrapped in an `rval`.\n\nAnd thirdly, the `valuate!` function takes an optional extra parameter.\nThe extra parameter is a function that post-processes the evaluation result of an RVal (after the post-processing of the `#rmap/ref` tagged literal).\nThe function receives a `clojure.lang.MapEntry` and should return a value.\nThe default is Clojure's `val`.\nThis way you can turn plain data into something else for example.\n\nLet's combine these three features in an example:\n\n```clj\n(def my-data-map {:foo 1 :bar #rmap/ref :foo})\n;=\u003e {:foo 1, :bar #rmap/ref :foo}\n\n(def my-rmap (rmap my-data-map))\n;=\u003e {:foo ??, :bar ??}\n\n(valuate! my-rmap (comp inc val))\n;=\u003e {:foo 2, :bar 3}\n```\n\n### Putting it all together\n\nAs you've seen in the appetizer, there is also a convenience macro called `rmap!`.\nThis is the same as `rmap`, but it is instantly valuated.\nFor example:\n\n```clj\n(rmap! {:foo 1\n        :bar (inc (ref :foo))\n        :baz (inc (ref :bar))})\n;=\u003e {:foo 1 :bar 2 :baz 3}\n```\n\nMaybe this `rmap!` is all you need for your purposes.\nThe other macros and functions are provided to give you all the tools you might need.\nThis way the rmap library aims to be both simple and easy.\n\n_That's it. Enjoy!_ 🚀\n\n## License\n\nCopyright © 2014-2024 Functional Bytes\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faroemers%2Frmap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faroemers%2Frmap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faroemers%2Frmap/lists"}