{"id":17141637,"url":"https://github.com/martinklepsch/derivatives","last_synced_at":"2025-05-05T22:16:15.479Z","repository":{"id":62433984,"uuid":"62583041","full_name":"martinklepsch/derivatives","owner":"martinklepsch","description":"🌱 Your companion to create derived values from a single source (atom)","archived":false,"fork":false,"pushed_at":"2019-05-17T18:44:44.000Z","size":129,"stargazers_count":112,"open_issues_count":4,"forks_count":16,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-05-05T22:16:06.064Z","etag":null,"topics":["clojurescript","dataflow","reactive","rum"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/martinklepsch.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-07-04T19:10:16.000Z","updated_at":"2023-05-10T09:41:35.000Z","dependencies_parsed_at":"2022-11-01T20:45:52.215Z","dependency_job_id":null,"html_url":"https://github.com/martinklepsch/derivatives","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinklepsch%2Fderivatives","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinklepsch%2Fderivatives/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinklepsch%2Fderivatives/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinklepsch%2Fderivatives/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/martinklepsch","download_url":"https://codeload.github.com/martinklepsch/derivatives/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252584334,"owners_count":21771945,"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":["clojurescript","dataflow","reactive","rum"],"created_at":"2024-10-14T20:26:04.497Z","updated_at":"2025-05-05T22:16:15.456Z","avatar_url":"https://github.com/martinklepsch.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Derivatives [![CircleCI](https://circleci.com/gh/martinklepsch/derivatives.svg?style=svg)](https://circleci.com/gh/martinklepsch/derivatives) [![cljdoc badge](https://cljdoc.xyz/badge/org.martinklepsch/derivatives)](https://cljdoc.xyz/d/org.martinklepsch/derivatives/CURRENT)\n\n*Subscriptions distilled.*\n\n[usage](#usage) | [comparisons](#comparisons) | [change log](CHANGES.md) | [API docs](https://cljdoc.xyz/d/org.martinklepsch/derivatives/CURRENT)\n\n**A note on terminology:** There are a lot of things with similar meanings/use-cases around: subscriptions, reactions, derived atoms, view models. \nI'll introduce another to make things even worse: *derivative*. A *derivative* implements `IWatchable` and it's value is the result of applying a function to the value of other things (*sources*) implementing `IWatchable`. Whenever any of the *sources* change the value of the *derivative* is updated.\n\n## Why this library\n\nLet's assume you're hooked about the idea of storing all your application state in a single atom (`db`). (Why this is a great idea is covered elsewhere.)\n\nMost of your components don't need the entirety of this state and instead receive a small selection of it that is enough to allow the components to do their job. Now that data is not always a subtree (i.e. `(get-in db ...)`) but might be a combination of various parts of your state. To transform the data in a way that it becomes useful for components you can use a function: `(f @db)`. \n\nNow you want to re-render your application whenever `db` changes so the views are representing the data in `db`. You end up calling `f` a lot, and remember, `f` has to do all the transformation for all components that **could** be rendered on the page, pretty inefficient!\n\nTo optimise we can create *derivatives* that contain data in shapes ideal to specific components and re-render those components when the *derivative* supplying the data changes.\n\nThese *derivatives* may depend on other *derivatives*, all ultimately leading up to your single `db` atom. To keep things efficient we only recalculate the value of a *derivative* when any of it's *sources* changes.\n\nThe intention of this library is to make the creation and usage of these interdependent references (*derivatives*) simple and efficient. \n\nA secondary objective is also to achieve the above without relying on global state being defined at the namespace level of this library. (See re-frame vs. pure-frame.)\n\n### What this library helps with\n\n- transform `db` into shapes suited for rendering (a.k.a. view models)\n- managing a pool of *derivatives* so only needed *derivatives* are created and freed as soon as they become unused (currently Rum specific)\n- server-side rendering (to some degree)\n\n### What this library doesn't help with\n\n- Ensuring the required data is in `db` (server/client rendering)\n- [Parameterized Subscriptions](#why-no-parameterized-subscriptions)\n\n## Usage\n\n[](dependency)\n```clojure\n[org.martinklepsch/derivatives \"0.3.1-alpha\"] ;; latest release\n```\n[](/dependency)\n\n*Derivatives* of your application state can be defined via a kind of specification like the one below:\n\n```clojure\n(def *db-atom (atom 0))\n\n(def drv-spec\n  {;; a source with no dependencies\n   :db     [[]         *db-atom]\n   ;; a derivative with a dependency\n   :inc    [[:db]      (fn [db] (inc db))]\n   ;; a derivative with multiple dependencies\n   :as-map [[:db :inc] (fn [db inc] {:db db :inc inc})]}\n```\n\nA specification like the above can be easily turned into a map with the same keys where the values are *derivatives* (see `org.martinklepsch.derivatives/build`).\n\nAlso it can be turned into a registry that can help with only creating needed derivatives and freeing them up when they become unused (see `org.martinklepsch.derivatives/derivatives-pool`).\n\n\u003e What follows is Rum specific and this library has a dependency on Rum but this pattern could be used with old Om apps, or even Reagent's reactions. I'm very open to changes in that direction.\n\nIn a Rum component tree you might use *derivatives* as follows (assuming `*db-atom` and `drv-spec` as above):\n\n```clojure\n(rum/defcs derived-view \u003c rum/reactive (d/drv :inc) (d/drv :as-map)\n  [s]\n  [:div\n   [:p \":inc \"    (-\u003e (d/react s :inc) pr-str)]\n   [:p \":as-map \" (-\u003e (d/react s :as-map) pr-str)]])\n\n(rum/defc app \u003c (d/rum-derivatives drv-spec)\n  [spec]\n  [:div\n   [:button {:on-click #(swap! *db-atom inc)} \"inc\"]\n   (derived-view)])\n```\n\nThe `rum-derivatives` mixin adds two functions to the React context of all child components: one to get a *derivative* and one to release it. The `drv` mixin adds hooks to your components that do exactly that and allow you to access the derivatives via component state. \n\n## Comparisons\n\n#### Plain `rum.core/derived-atom`\n\nRum's derived-atoms serve as building block in this library but there are some things which are (rightfully) not solved by derived-atoms:\n\n- Creation of interdependent *derivative* chains and\n- a mechanism to only create actually needed derived-atoms.\n\nA small code sample should illustrate this well:\n\n```clojure\n(def *db (atom {:count 0})) ; base db\n\n(def *increased \n  (rum/derived-atom [*db]\n                    ::increased \n                    (fn [db]\n                      (inc (:count db)))))\n  \n(def *as-map\n  (rum/derived-atom [*db *increased] \n                    ::as-map \n                    (fn [db incd] \n                      {:db db :increased incd})))\n```\n\ncompared with the way this could be described using *derivatives*:\n\n```clojure\n(def *db (atom {:count 0}))\n\n(def spec\n  ;; {name    [depends-upon     derive-fn]}\n  {:db        [[]               *db]\n   :increased [[:db]            (fn [db] (inc (:count db)))]\n   :as-map    [[:db :increased] (fn [db incd] {:db db :increased incd})]})\n```\n\nThe benefit here is that we don't use vars to make sure the dependencies are met and that we provide this information in a way that can easily be turned into a dependency graph (data FTW) which will later help us only calculating required *derivatives* (done by `derivatives-pool`). In comparison the first snippet will create derived-atoms and recalculate them whenever any of their dependencies change, no matter if you're using the derived-atom in any of your views.\n\n\n#### Re-Frame Subscriptions\n\nThe way they work Re-Frame's dynamic subscriptions are not much different from the approach chosen here, they vary in two ways however:\n\n- In Re-Frame you can do `(subscribe [:sub-id \"a parameter\"])`, with *derivatives* you can't. Instead these parameters need to be put into `db` and be used (potentially via another *derivative*) from there.\n- In Re-Frame subscriptions may have side-effects to listen to remote changes etc. This library does not intend to solve this kind of problem and thus side effects are discouraged.\n\n#### Why no parameterized subscriptions?\n\nIn my personal experience a lot of non-idiomatic, non performance\noptimal re-frame use comes from having subscriptions in every corner\nof the code. Parameterized subscriptions enable this even more.\n\nWhile a bandaid more than a solution the lack of parameterized\nsubscriptions in Derivatives is meant to discourage ad-hoc, throwaway\nsubscription use and instead encourage thoughtful reshaping of data\nfrom your DB into a form suitable for rendering.\n\n## Contributing\n\nFeedback and PRs welcome.\n\nTests can be run with `boot --source-paths test test` or `boot -s test test`.\n\n--\n\n**License:** MPLv2, see `LICENSE`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinklepsch%2Fderivatives","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmartinklepsch%2Fderivatives","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinklepsch%2Fderivatives/lists"}