{"id":15010648,"url":"https://github.com/tolitius/calip","last_synced_at":"2025-04-05T06:01:27.426Z","repository":{"id":62434941,"uuid":"97964927","full_name":"tolitius/calip","owner":"tolitius","description":"calip(er): all functions deserve to be measured and debugged at runtime","archived":false,"fork":false,"pushed_at":"2025-03-15T17:22:15.000Z","size":378,"stargazers_count":78,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-29T05:01:40.566Z","etag":null,"topics":["clojure","debug","performance","trace"],"latest_commit_sha":null,"homepage":"","language":"Clojure","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/tolitius.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,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-07-21T15:58:20.000Z","updated_at":"2025-03-15T17:22:19.000Z","dependencies_parsed_at":"2025-03-22T04:00:23.215Z","dependency_job_id":"38ddccd2-fefc-44a7-aaf6-440a35b84857","html_url":"https://github.com/tolitius/calip","commit_stats":{"total_commits":34,"total_committers":1,"mean_commits":34.0,"dds":0.0,"last_synced_commit":"93b77286fad8e58beebce43bec4a1c5b493e2d7c"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fcalip","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fcalip/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fcalip/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fcalip/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tolitius","download_url":"https://codeload.github.com/tolitius/calip/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247294514,"owners_count":20915340,"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","debug","performance","trace"],"created_at":"2024-09-24T19:35:12.325Z","updated_at":"2025-04-05T06:01:27.358Z","avatar_url":"https://github.com/tolitius.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# calip(er)\n\nmeasuring, tracing and debugging functions on demand _**without**_ a need to alter the code\n\n[![Clojars Project](http://clojars.org/tolitius/calip/latest-version.svg)](http://clojars.org/tolitius/calip)\n\n- [what does it do?](#what-does-it-do)\n- [performance on demand](#performance-on-demand)\n- [taming runtime errors](#taming-runtime-errors)\n  - [measuring on error](#measuring-on-error)\n- [reporting](#reporting)\n  - [custom reporting](#custom-reporting)\n  - [custom reports on errors](#custom-reports-on-errors)\n- [µ/trace them!](#%C2%B5trace-them)\n  - [input arguments](#input-arguments)\n  - [respect the context](#respect-the-context)\n  - [reveal the beauty](#reveal-the-beauty)\n- [match and wrap many functions](#match-and-wrap-many-functions)\n- [license](#license)\n\n## what does it do?\n\ncalip _measures_, _traces_, and _debugs_ functions on demand, or in case of an error..\u003cbr/\u003e\n:sunglasses: _**without**_ a need to alter the code\n\nit comes really handy at development time, as well as for deployed applications:\n\n* when you need _on demand_ performance metrics with runtime arguments\n* when you need to trace a sequence of functions called\n* when you need to see the actual runtime function arguments in case of an error\n* when you need to see the actual runtime function arguments as the program is running\n\nin which case you can just connect to a deployed application via an `nREPL`, and add measurements, traces, logs to _any_ :mag_right: \"functional suspect\".\n\n## performance on demand\n\nlet's pretend we have an app with two functional suspects:\n\n\u003e _if playing from the calip source dir you can:\u003cbr/\u003e$ make repl_\n\n```clojure\n=\u003e (defn rsum [n] (reduce + (range n)))\n#'user/rsum\n=\u003e (defn rmult [n] (reduce *' (range 1 n)))\n#'user/rmult\n\n=\u003e (rsum 10)\n45\n=\u003e (rmult 10)\n362880\n```\n\nnow let's measure them:\n\n```clojure\n=\u003e (require '[calip.core :as calip])\n\n=\u003e (calip/measure #{#'user/rsum\n                    #'user/rmult})\n\n=\u003e (rsum 10)\n\"#'user/rsum\" args: (10) | took: 13,969 nanos | returned: 45\n45\n\n=\u003e (rmult 10)\n\"#'user/rmult\" args: (10) | took: 16,402 nanos | returned: 362880\n362880\n```\n\n`(10)` here shows the runtime arguments to a function that is measured, or \"an\" argument in this case.\n\nthese measurements can be removed of course:\n\n```clojure\n=\u003e (calip/uncalip #{#'user/rsum})\n\n=\u003e (rsum 10)\n45\n=\u003e (rmult 10)\n\"#'user/rmult\" args: (10) | took: 17,479 nanos | returned: 362880\n362880\n```\n\nor remove it from both:\n\n```clojure\n=\u003e (calip/uncalip #{#'user/rsum #'user/rmult})\n\n=\u003e (rsum 10)\n45\n=\u003e (rmult 10)\n362880\n```\n\n\u003e _alternitevely all functions that are wrapped via calip can be removed with:_\n\u003e ```clojure\n\u003e =\u003e (calip/uncalip (calip/wrapped))\n\u003e remove a wrapper from #'user/rmult\n\u003e remove a wrapper from #'user/rsum\n\u003e ```\n\n## taming runtime errors\n\nmost of the time, in case of a runtime error/exception, JVM reports an array of stack trace elements, each representing one stack frame. This array is also known as a stacktrace.\n\nwhile it is immensely useful for tracking down an error scope (i.e. _where_ it happened), it falls short to provide a _state_ snapshot at the time an error occurred: i.e. \"what were the arguments passed to a function _at the time_ the error occurred?\"\n\n`calip` helps tracking down these runtime arguments by setting an \"`:on-error?`\" flag on a measurement.\n\n### measuring on error\n\nas an example let's take a function that creates a socket (i.e. connects) to external systems:\n\n```clojure\n=\u003e (defn connect [{:keys [host port]}]\n     (java.net.Socket. host port))\n```\n```clojure\n=\u003e (connect {:host \"my-good-host.com\" :port 7889})\n#object[java.net.Socket 0x59cdc19b \"Socket[addr=my-good-host.com/10.X.X.23,port=7889,localport=62446]\"]\n\n=\u003e (connect {:host \"8.8.8.8\" :port 1025})\n\njava.net.ConnectException: Operation timed out\n\n=\u003e (connect {:host \"127.0.0.1\" :port 1025})\n\njava.net.ConnectException: Connection refused\n```\n\nin case of an error JVM reports an exception but there is no visual on what the arguments were at the time of this exception.\n\nlet's fix it _without a code change_ / on a running application:\n\n```clojure\n=\u003e (calip/measure #{#'user/connect} {:on-error? true})\n```\n\nwe can still normally connect without any extra logging / metrics:\n\n```clojure\n=\u003e (connect {:host \"my-good-host.com\" :port 7889})\n#object[java.net.Socket 0x3bc7a27c \"Socket[addr=my-good-host.com/10.X.X.23,port=7889,localport=62446]\"]\n```\n\nbut in case of an error, in addition to the time a function took, `calip` will report the actual runtime args that led to this error:\n\n```clojure\n=\u003e (connect {:host \"8.8.8.8\" :port 1025})\n\"#'user/connect\" args: ({:host \"8.8.8.8\", :port 1025}) | took: 75,696,573,373 nanos | error: java.net.ConnectException: Operation timed out\n\njava.net.ConnectException: Operation timed out\n```\n\n```clojure\n=\u003e (connect {:host \"127.0.0.1\" :port 22})\n\"#'user/connect\" args: ({:host \"127.0.0.1\", :port 22}) | took: 309,753 nanos | error: java.net.ConnectException: Connection refused\n\njava.net.ConnectException: Connection refused\n```\n\n\u003e _`:on-error?` flag can be combined with a custom `:report` function that is documented in the next section_\n\n## reporting\n\nby default calip will use `println` and a \"default format\" as shown above to report metrics, but it is pluggable.\u003cbr/\u003e\nyou can pass a report function to `calip/measure`. calip would pass a map to this function with:\n\n```clojure\n{:took took           ;; time this function took to execute in nanoseconds\n :fname fname         ;; function name with a namespace\n :args args           ;; arguments that were passed to this function\n :returned / :error}  ;; a :returned value or an :error [depending on whether the :on-error? flag is set]\n```\n\nquite a useful scenario is to use calip to measure or debug parts of the application that writes logs. We can tap into that:\n\n```clojure\n=\u003e (require '[clojure.tools.logging :as log])\n\n=\u003e (calip/measure #{#'user/rsum #'user/rmult} {:report #(log/info (calip/default-format %))})\n\n=\u003e (rsum 10)\n13:42:04.048 [nREPL-worker-24] INFO  user - \"#'user/rsum\" args: (10) | took: 14,928 nanos | returned: 45\n45\n=\u003e (rmult 10)\n13:42:07.687 [nREPL-worker-24] INFO  user - \"#'user/rmult\" args: (10) | took: 16,280 nanos | returned: 362880\n362880\n```\n\nnotice we used `(calip/default-format %)` to format that `{:took .., :fname .., :args .., :returned}` map, but you can of course customize it.\n\n### custom reporting\n\n```clojure\n=\u003e (defn create-life [{:keys [galaxy planet]}] \"creating life...\")\n#'user/create-life\n=\u003e\n\n=\u003e (create-life {:galaxy \"pegasus\" :planet \"athos\"})\n\"creating life...\"\n\n=\u003e (calip/measure #{#'user/create-life} {:report (fn [{:keys [took fname]}]\n                                                   (log/info fname \"took\" took \"ns\"))})\n\n=\u003e (create-life {:galaxy \"pegasus\" :planet \"athos\"})\n13:54:20.334 [nREPL-worker-25] INFO  user - #'user/create-life took 2637 ns\n\"creating life...\"\n```\n\nor with args and return values:\n\n```clojure\n=\u003e (calip/measure #{#'user/create-life} {:report (fn [{:keys [took fname args returned]}]\n                                                   (log/info \"\\n|\u003e\" fname\n                                                             \"\\n|\u003e with args:\" args\n                                                             \"\\n|\u003e took:\" took\n                                                             \"ns \\n|\u003e return value:\" returned))})\n\n=\u003e (create-life {:galaxy \"pegasus\" :planet \"athos\"})\nINFO  user -\n|\u003e #'user/create-life\n|\u003e with args: ({:galaxy pegasus, :planet athos})\n|\u003e took: 2911 ns\n|\u003e return value: creating life...\n\n\"creating life...\"\n```\n\n### custom reports on errors\n\na custom `:report` function can be combined with an `:on-error?` flag:\n\n```clojure\nuser=\u003e (calip/measure #{#'user/connect}\n                      {:on-error? true\n                       :report #(log/info (calip/default-format %))})\n\nuser=\u003e (connect {:host \"127.0.0.1\" :port 22})\nINFO  user - \"#'user/connect\" args: ({:host \"127.0.0.1\", :port 22}) | took: 339,019 nanos | error: java.net.ConnectException: Connection refused\n```\n\nor\n\n```clojure\nuser=\u003e (calip/measure #{#'user/rsum}\n                      {:report #(log/info (calip/default-format %))\n                       :on-error? true})\n\nuser=\u003e (rsum \"oops\")\nINFO  user - \"#'user/rsum\" args: (\"oops\") | took: 87,268 nanos | error: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number\n```\n\n## µ/trace them!\n\n[µ/log](https://github.com/BrunoBonacci/mulog) is a great logging and tracing lib that can be used with calip instead of custom or built in reporting functions.\n\nbesides benefits of picking up µ/log's [context](https://github.com/BrunoBonacci/mulog#use-of-context) it will\nalso catch and report exceptions that would include duration and more tasty details.\n\nin order to use µ/log, you would need to start one of the [publishers](https://github.com/BrunoBonacci/mulog#publishers).\u003cbr/\u003e\nfor this example a console pretty publisher does it:\n\n```clojure\n=\u003e (require '[com.brunobonacci.mulog :as µ])\n\n=\u003e (def pub (µ/start-publisher! {:type :console :pretty? true}))\n#'user/pub\n```\n\nand now unleash the beast of [µ/trace](https://github.com/BrunoBonacci/mulog#%CE%BCtrace) with the `calip/trace` function.\n\nlet's define a couple of functions:\n\n```clojure\n=\u003e (defn rsum [n] (reduce + (range n)))\n#'user/rsum\n=\u003e (defn rmult [n] (reduce *' (range 1 n)))\n#'user/rmult\n```\n\nand trace'em without them knowing (i.e. :gift: wrap them in µ/trace):\n\n```clojure\n=\u003e (calip/trace #{#'user/rsum\n                  #'user/rmult})\nwrapping #'user/rsum in µ/trace\nwrapping #'user/rmult in µ/trace\n\n=\u003e (rsum 10)\n45\n{:mulog/event-name :user/rsum,\n :mulog/timestamp 1666377944748,\n :mulog/trace-id #mulog/flake \"4m-duM3W-Trkqocc7kDcxlHbhDwPPn5l\",\n :mulog/root-trace #mulog/flake \"4m-duM3W-Trkqocc7kDcxlHbhDwPPn5l\",\n :mulog/duration 53261,\n :mulog/namespace \"calip.core\",\n :mulog/outcome :ok}\n```\n\nby default calip will use a function name for a `:mulog/event-name` key\u003cbr/\u003e\nsince `µ/trace` takes a custom event name and other options, such as `:pairs`, `:capture`, etc..\u003cbr/\u003e\nwe can pass all of it in a map of options:\n\n```clojure\n=\u003e (calip/trace #{#'user/rsum\n                  #'user/rmult} {:event-name ::calculator\n                                 :pairs [:foo 42 :bar :zoo]\n                                 :capture (fn [result] {:result-is result})})\nwrapping #'user/rsum in µ/trace\nwrapping #'user/rmult in µ/trace\n\n=\u003e (rmult 42)\n33452526613163807108170062053440751665152000000000N\n{:mulog/event-name :user/calculator,\n :mulog/timestamp 1666378105154,\n :mulog/trace-id #mulog/flake \"4m-e2gb3DA5xsFXSLRUHLnypQGyQXELt\",\n :mulog/root-trace #mulog/flake \"4m-e2gb3DA5xsFXSLRUHLnypQGyQXELt\",\n :mulog/duration 405866,\n :mulog/namespace \"calip.core\",\n :mulog/outcome :ok,\n :bar :zoo,\n :foo 42,\n :result-is 33452526613163807108170062053440751665152000000000N}\n```\n\nnotice:\n\n* `:foo` and `:bar` in a trace from `:pairs`\n* `:mulog/event-name` is `:user/calculator`\n* and the result is captured in `:result-is`\n\n---\nwe can remove traces by `calip/untrace`:\n\n```clojure\n;; removing previous µ/trace(s):\n=\u003e (calip/untrace #{#'user/rsum #'user/rmult})\nremove a wrapper from #'user/rsum\nremove a wrapper from #'user/rmult\n```\n\n\n### input arguments\n\n`calip/trace` can also take a `:format-args` option that, if provided, would format and add a function input arguments to the trace:\n\n```clojure\n=\u003e (calip/trace #{#'user/rsum\n                  #'user/rmult} {:format-args #(-\u003e\u003e % first (str \"meaning of life universe and everything: \"))\n                                 :pairs [:foo 42 :bar :zoo]\n                                 :capture (fn [result] {:result-is result})})\nwrapping #'user/rsum in µ/trace\nwrapping #'user/rmult in µ/trace\n\n=\u003e (rmult 42)\n33452526613163807108170062053440751665152000000000N\n{:mulog/event-name :user/rmult,\n :mulog/timestamp 1666378492125,\n :mulog/trace-id #mulog/flake \"4m-ePDEGgMb2Xq3PH-9C6FIUu2R37djV\",\n :mulog/root-trace #mulog/flake \"4m-ePDEGgMb2Xq3PH-9C6FIUu2R37djV\",\n :mulog/duration 324314,\n :mulog/namespace \"calip.core\",\n :mulog/outcome :ok,\n :args \"meaning of life universe and everything: 42\",\n :bar :zoo,\n :foo 42,\n :result-is 33452526613163807108170062053440751665152000000000N}\n```\n\nnotice the `:args` key 👆 in the trace, it reveals the meaning.\n\n#### separate keys for arguments\n\nby default the \"`format-args`\" function records all the arguments under \"`:args`\" in the trace.\u003cbr/\u003e\nhowever, if it returns **a map**, keys of this map are recorded _separately_ in a trace\u003cbr/\u003e\nsimilar to dynamic values in \"pairs\":\n\n```clojure\n=\u003e (defn stargaze [constellation system star]\n     (print \"looking at stars..\"))\n```\n\n```clojure\n=\u003e (calip/trace [#'user/stargaze] {:format-args (fn [[c cs s]]\n                                                  {:arg/constellation c\n                                                   :arg/system cs\n                                                   :arg/star s})})\n```\n\n\u003e _they don't need to be prefixed/namespaced with \"`arg/`\", this is just to visually group them together_\n\n```clojure\n=\u003e (stargaze \"centaurus\" \"alpha centauri\" \"toliman\")\nnil\nlooking at stars..\n{:mulog/event-name :user/stargaze,\n :mulog/timestamp 1709917686018,\n :mulog/trace-id #mulog/flake \"4ves6lMDdexpjqZ1h8pkiAYWw0f0FEda\",\n :mulog/root-trace #mulog/flake \"4ves6lMDdexpjqZ1h8pkiAYWw0f0FEda\",\n :mulog/duration 39702,\n :mulog/namespace \"calip.core\",\n :mulog/outcome :ok,\n\n ;; here they are\n\n :arg/constellation \"centaurus\",\n :arg/star \"toliman\",\n :arg/system \"alpha centauri\"}\n```\n\nplug these values directly into James Webb Space Telescope, and...\n\n\u003cimg src=\"https://github.com/tolitius/calip/assets/136575/f88339b1-b7da-4408-84d6-1b8936898cde\" width=\"500px\"/\u003e\n\n\n### respect the context\n\nif a global [context](https://github.com/BrunoBonacci/mulog#use-of-context) is set (by the µ/log) before or after measure is called, it will be included in the trace:\n\n```clojure\n=\u003e (µ/set-global-context! {:app-name \"sum and mult\"\n                           :version \"0.1.0\"\n                           :env \"local\"})\n\nuser=\u003e (rsum 10)\n45\n{:mulog/event-name :user/find-life,\n :mulog/timestamp 1666233540292,\n :mulog/trace-id #mulog/flake \"4lyaZuV3b4QjLuE-lHOTvvObz5X0814O\",\n :mulog/root-trace #mulog/flake \"4lyaZuV3b4QjLuE-lHOTvvObz5X0814O\",\n :mulog/duration 6651,\n :mulog/namespace \"calip.core\",\n :mulog/outcome :ok,\n :app-name \"sum and mult\",\n :bar :zoo,\n :env \"local\",\n :foo 42,\n :version \"0.1.0\"}\n```\n\nsame applies for the local context (set by the µ/log) that is set at runtime, after measure was called:\n\n```clojure\n=\u003e (µ/with-context {:who-am-i \"calculator\"}\n     (rsum 10))\n45\n{:mulog/event-name :user/find-life,\n :mulog/timestamp 1666233724101,\n :mulog/trace-id #mulog/flake \"4lyajbDBvVfWFZzeo8OkUeBzQxjgHiWY\",\n :mulog/root-trace #mulog/flake \"4lyajbDBvVfWFZzeo8OkUeBzQxjgHiWY\",\n :mulog/duration 199814,\n :mulog/namespace \"calip.core\",\n :mulog/outcome :ok,\n :app-name \"sum and mult\",\n :bar :zoo,\n :env \"local\",\n :foo 42,\n :version \"0.1.0\",\n :who-am-i \"calculator\"}\n```\n\n### reveal the beauty\n\nsince functions can be traced with calip without them knowing it we can hook into any application and create beautiful visuals.\n\nthis example uses [zipkin](https://github.com/BrunoBonacci/mulog/tree/master/mulog-zipkin), but any tracing visual tool (grafana, jaeger, sleuth, kibana, etc.) can be used.\n\nlet's binge the [Star Wars episodes](dev/star_wars.clj) and see how long it would take us:\n\n```clojure\n=\u003e (require '[com.brunobonacci.mulog :as μ]\n            '[calip.core :as c]\n            '[star-wars])\n\n;; starting a different publisher that would send µ/trace output to zipkin\n=\u003e (μ/start-publisher!  {:type :zipkin\n                         :url  \"http://localhost:9411/\"})\n```\n\nthis example is contrived on purpose, usually it'd be something like \"`#'foo.bar/find-*`\" or \"`#'foo.bar/baz`\":\n\n```clojure\n=\u003e (c/trace #{\"#'star-wars/the-*\"\n              \"#'star-wars/re*\"\n              \"#'star-wars/a*\"\n              #'star-wars/one-offs\n              #'star-wars/rogue-one\n              #'star-wars/solo\n              #'star-wars/binge})\n\nwrapping #'star-wars/one-offs in µ/trace\nwrapping #'star-wars/rogue-one in µ/trace\nwrapping #'star-wars/the-force-awakens in µ/trace\nwrapping #'star-wars/the-rise-of-skywalker in µ/trace\nwrapping #'star-wars/a-new-hope in µ/trace\nwrapping #'star-wars/attack-of-the-clones in µ/trace\nwrapping #'star-wars/binge in µ/trace\nwrapping #'star-wars/the-phantom-menace in µ/trace\nwrapping #'star-wars/the-empire-strikes-back in µ/trace\nwrapping #'star-wars/return-of-the-jedi in µ/trace\nwrapping #'star-wars/solo in µ/trace\nwrapping #'star-wars/the-last-jedi in µ/trace\nwrapping #'star-wars/revenge-of-the-sith in µ/trace\n```\n\nready to binge? let's do it!\n\n```clojure\n=\u003e (star-wars/binge)\n```\n\n![zipkin trace](doc/img/binge-starwars.png)\n\nbeauty unlocked :nerd_face:\n\nall these Star Wars characters play a role of different \"applications\".\n\n## match and wrap many functions\n\nwhile profiling applications there are two questions that are very frequent:\n\n\u003e out of all these functions what _exactly_ takes so long?\n\nand\n\n\u003e how long does _each function_ take in this module (namespace)?\n\ninstead of explicitly listing all the functions in a particular namespace, `calip` accepts strings in a:\n\n* `\"#'foo.bar/prefix-*\"` format that would expand to include function names that starts with \"`prefix-`\" in a particular namespace\n* `\"#'foo.bar/*\"` format that would expand to include all the functions in a particular namespace\n\nfor example wrap only functions in a `user` namespace that start with \"`r`\":\n\n```clojure\nuser=\u003e (calip/measure #{\"#'user/r*\"})\n```\n```clojure\nadding hook to #'user/rmult\nadding hook to #'user/rsum\n```\n\nor _all_ of the functions in the `user` ns:\n\n```clojure\nuser=\u003e (calip/measure #{\"#'user/*\"})\n```\n\nwould add \"hooks\" to:\n\n```clojure\nadding hook to #'user/+version+\nadding hook to #'user/check-sources\nadding hook to #'user/dev\nadding hook to #'user/log4b\nadding hook to #'user/rmult\nadding hook to #'user/rsum\n```\n\ni.e. it expands `\"#'user/*\"` into all the `'user` functions currently known to the runtime.\n\n## license\n\nCopyright © 2024 tolitius\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%2Ftolitius%2Fcalip","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftolitius%2Fcalip","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Fcalip/lists"}