{"id":26194252,"url":"https://github.com/active-group/active-clojure","last_synced_at":"2025-09-13T07:16:47.721Z","repository":{"id":21189692,"uuid":"24497699","full_name":"active-group/active-clojure","owner":"active-group","description":"A library with various basic utilities for programming with Clojure.","archived":false,"fork":false,"pushed_at":"2025-08-31T11:32:13.000Z","size":944,"stargazers_count":26,"open_issues_count":6,"forks_count":4,"subscribers_count":15,"default_branch":"main","last_synced_at":"2025-08-31T13:20:28.441Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/active-group.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2014-09-26T11:53:39.000Z","updated_at":"2025-08-31T11:32:16.000Z","dependencies_parsed_at":"2023-02-16T23:00:45.070Z","dependency_job_id":"1c71bd9a-bf10-4e23-aacd-cb56cba2610d","html_url":"https://github.com/active-group/active-clojure","commit_stats":{"total_commits":622,"total_committers":15,"mean_commits":41.46666666666667,"dds":0.7556270096463023,"last_synced_commit":"f3c6d9c6a5da2fc60ecf06ecd3fe37724b99413e"},"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"purl":"pkg:github/active-group/active-clojure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-clojure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-clojure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-clojure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-clojure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/active-group","download_url":"https://codeload.github.com/active-group/active-clojure/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-clojure/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274932272,"owners_count":25376125,"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-09-13T02:00:10.085Z","response_time":70,"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":[],"created_at":"2025-03-12T01:55:56.252Z","updated_at":"2025-09-13T07:16:47.708Z","avatar_url":"https://github.com/active-group.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Active Clojure\n\n## Breaking changes in recent releases\n\nA library with various basic utilities for programming with Clojure.\n\n[![Clojars Project](https://img.shields.io/clojars/v/de.active-group/active-clojure.svg)](https://clojars.org/de.active-group/active-clojure)\n[![Actions Status](https://github.com/active-group/active-clojure/workflows/ci/badge.svg)](https://github.com/active-group/active-clojure/actions)\n[![cljdoc badge](https://cljdoc.org/badge/de.active-group/active-clojure)](https://cljdoc.org/d/de.active-group/active-clojure/CURRENT)\n\n### `0.40.0`\n\n- `active.clojure.monad/run-monadic-swiss-army` was renamed to\n  `active.clojure.monad/run-monadic`.\n\n### `0.38`\n\n- For an RTD record `MyRecord`, `(MyRecord :meta)` will no longer\n  return a meta data map. Use `(meta #'MyRecord)` instead.\n\n### Since `0.28.0`\n\n- Clojure version 1.9.0 or higher and Clojurescript version 1.9.542 or higher\n  are required.\n- The namespace of ClojureScript's `define-record-type` has changed from\n  `active.clojure.record` to `active.clojure.cljs.record`.\n- To make sure that the right `active-clojure` version gets picked up by\n  Leiningen, you should exclude previous `active-clojure` that are included in\n  the dependencies transitively by adding `:exclusions [active-clojure]` to\n  libraries that come with the dependency.  When in doubt, check `lein deps :why\n  active-clojure`.\n- Since selectors are now lenses by default, the previously used \"lens triples\"\n  are no longer valid. You need to remove the parens and the third element and\n  use the selector instead of the name of the lens everywhere in your code.\n\n## Usage\n\n### Records\n\nThe `active.clojure.record` namespace implements a `define-record-type` form\nsimilar to Scheme's [SRFI 9](http://srfi.schemers.org/srfi-9/).\n\nExample: A card consists of a number and a color\n\n```clojure\n(ns namespace\n  (:require [active.clojure.record :as r]))\n\n(r/define-record-type Card\n  (make-card number color)\n  card?\n  [number card-number\n   color card-color])\n\n;; Creating a record with field values 3 and \"hearts\"\n(def card-1 (make-card 3 \"hearts\"))\n;; Get number of this card via selector\n(card-number card-1)\n;; =\u003e 3\n;; Predicate test\n(card? card-1)\n;; =\u003e true\n(card? \"3 of Hearts\")\n;; =\u003e false\n```\n\nYou can provide additional options in an option-map as second argument to `define-record-type`.\n\n#### Specs\n\nBy providing a value to the option key `:spec`, a spec for the record type is created.\nThe fields of records can also be \"spec'd\" via meta information.\n\n```clojure\n(spec/def ::color #{:diamonds :hearts :spades :clubs})\n\n(defn is-valid-card-number?\n  [n]\n  (and (int? n)\n       (\u003e n 0) (\u003c n 14)))\n\n(r/define-record-type Card\n  {:spec ::card}\n  (make-card number color)\n  card?\n  [^{:spec is-valid-card-number?} number card-number\n   ^{:spec ::color} color card-color])\n\n(spec/valid? ::card (make-card 5 :hearts))\n;; =\u003e true\n(spec/valid? ::card (make-card 5 \"hearts\"))\n;; =\u003e false\n(spec/explain ::card (make-card 5 \"hearts\"))\n;; =\u003e val: #namespace.Card{:number 124, :color \"hearts\"} fails spec: :namespace/card\n;;    predicate: (valid? :namespace/color (card-color %))\n```\n\nTo use `spec/def`, `spec/valid?`, and `spec/explain` you have to require `clojure.spec.alpha`\nin your `ns` form.\n\nYou also get a spec for the constructor function. If instrumentation is enabled\n(via `clojure.spec.test.alpha/instrument`), the constructor is checked using the specs\nprovided for the selector functions:\n\n```clojure\n;; Does not get checked without instrument.\n(make-card 20 :hearts)\n;; =\u003e #namespace.Card{:number 20 :color :hearts}\n\n;; Now, with instrumentation.\n(clojure.spec.test.alpha/instrument)\n\n(make-card 20 :hearts)\n;; =\u003e Spec assertion failed.\n;;\n;; Spec: #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x31346221\n;; \"clojure.spec.alpha$regex_spec_impl$reify__2436@31346221\"]\n;; Value: (20 :hearts)\n;;\n;; Problems:\n;;\n;; val: 20\n;; in: [0]\n;; failed: is-valid-card-number?\n;; at: [:args :number]\n```\n\n#### Non generative option\n\nIf you provide a value (uid) to the `nongenerative` option, the record-creation\noperation is nongenerative i.e., a new record type is created only if no\nprevious call to `define-record-type ` was made with the uid. Otherwise, an\nerror is thrown. If uid is `true`, a uuid is created automatically. If this\noption is not given (or value is falsy), the record-creation operation is\ngenerative, i.e., a new record type is created even if a previous call to\n`define-record-type` was made with the same arguments.\n\n#### Arrow constructor\n\nDefault is `true`.\n\nIf you provide the key:val pair `:arrow-constructor?`:`false`, the creation of\nthe arrow constructor of the `defrecord` call is omitted, i.e.\n\n```clojure\n(define-record-type Test {:arrow-constructor? false} (make-test a) ...)\n```\n\nwon't create a function `-\u003eTest`.\n\n#### Map protocol\n\nDefault is `true`.\n\nIf you don't want your records to implement the Map protocols (in *Clojure*\nthese are `java.util.Map` and `clojure.lang.IPersistentMap`, in *ClojureScript*\n`IMap` and `IAssociative`), you can provide the key:val pair\n`:map-protocol?`:`false` to the options map.\n\n#### Remove default interfaces/protocols\n\nThere are a number of interfaces, that our records implement by default (like\ne.g. aforementioned `java.util.Map`). Providing key:val pair\n`:remove-interfaces`:`[interface1 interface2 ...]` will prevent the\nimplementations of the given interfaces.\n\n#### Providing own implementations of interfaces and protocols\n\nYou can implement protocols and interfaces with the\n`define-record-type`-statement:\n\n```clojure\n(defprotocol SaySomething\n  (say [this]))\n\n(r/define-record-type Card\n  (make-card number color)\n  card?\n  [number card-number\n   color card-color]\n  SaySomething\n  (say [this] (str \"The card's color is \" (card-color this))))\n\n(say (make-card 3 :hearts))\n```\n\nYou can also override the defaultly implemented interfaces/protocols by the same\nmeans. You don't have to provide every method of a default interface, those left\nout by you will remain the default ones.\n\n#### Java Classes, RTD records\n\nBy default `define-record-type` generates new types in the host\nlanguage (Java for Clojure or JavaScript for ClojureScript), just\nlike `defrecord` does. That can be changed by specifying either\n`:java-class? false`, or `rtd-record? true` options like so:\n\n```clojure\n(r/define-record-type Foo {:rtd-record? true}\n ...)\n```\n\nThese records have the advantage, that a hot code reload of the same\ndefinition will not create a new type in the host language. So record\nvalues created before the code reload are still compatible with the\nrecord type, unless its fields have changed of course.\n\nYou cannot define protocol implementations for these kinds of record\ntypes, but you can use multi methods. Use the defined type and the\nresult of `r/record-type` as the dispatch value for that.\n\nAbove options may not work with RTD records:\n\n- Arrow: RTD records don't provide an arrow constructor\n- Map implementation: RTD records don't implement the map interface\n- Interfaces: No interfaces are implemented, you cannot provide your own\n  implementations for RTD records\n\n#### Meta data\n\nYou can provide meta data via `(define-record-type ^{:foo \"bar\"}\nMyRecord)`. This meta data is then \"inherited\" to all created symobls\n(like `-\u003eMyRecord`).\n\nIf you use an RTD record (`:java-class?`, `:rtd-record?` options), this data\nis also retrievable via `(meta #'MyRecord)`.\n\n#### Projection lens\n\nYou can provide a binding name to the option key `:projection-lens-constructor` to create a\n[[active.clojure.lens/record-lens]] constructor for the record that is bound to the supplied\nbinding name.  For example:\n\n```clojure\n(define-record-type Pare\n  {:projection-lens-constructor pare-lens}\n  kons\n  pare?\n  [a kar\n   b kdr])\n\n(let [data {:pare {:a \"Foo\" :b \"Bar\"}}\n      l (pare-lens (lens/\u003e\u003e :pare :a) (lens/\u003e\u003e :pare :b))]\n    (= (pare \"Foo\" \"Bar\") (lens/yank data l)))\n```\n\n### Lenses\n\nThe `active.clojure.lens` namespace implements *lenses*.  Lenses\nprovide a subtle way to access and update the elements of a structure\nand are well-known in [functional programming\nlanguages](http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html).\n\n#### Records example\n\nIf you want to update only one field in a record, it is cumbersome to write out\nthe whole make-constructor expression:\n\n```clojure\n(r/define-record-type Person\n  make-person\n  person?\n  [name person-name\n   age person-age\n   address person-address\n   job person-job])\n\n(def mustermann (make-person \"Max Mustermann\" 35 \"Hechinger Straße 12/1, 72072 Tübingen\"\n                             \"Software Architect\"))\n\n(make-person \"Max Maier\"\n             (person-age mustermann)\n             (person-address mustermann)\n             (person-job mustermann))\n```\n\nWith lenses you can set and update fields easily:\n\n```clojure\n(lens/shove mustermann\n            person-name\n            \"Max Maier\")\n\n(lens/overhaul mustermann\n               person-age\n               inc)\n```\n\n**Note:** The `lens` functions don't alter the given record but create and return\na new one.\n\nYou can even combine lenses to update records inside records:\n\n```clojure\n(r/define-record-type Address\n  make-address\n  address?\n  [street address-street\n   number address-number\n   city address-city\n   postal-code address-postal-code])\n\n(def mustermann (make-person \"Max Mustermann\" 35\n                             (make-address \"Hechinger Strasse\" \"12/1\"\n                                           \"Tübingen\" 72072)\n                             \"Software Architect\"))\n\n(lens/shove mustermann\n            (lens/\u003e\u003e person-address address-street)\n            \"Hechinger Straße\")\n```\n\n### Conditions\n\nThe `active.clojure.condition` namespace implements *conditions*,\nspecifically crafted exception objects that form a protocol for\ncommunicating the cause of an exception, similar to the condition\nsystem in [R6RS Scheme](http://r6rs.org/).\n\n### Configuration\n\nThe `active.clojure.config` namespace implements application\nconfiguration via a big map.\n\n### Debugging\n\nThe `active.clojure.debug` namespace implements some useful debugging\ntools such as a macro `pret` that prints and returns its argument.\n\n### Pattern Matching\n\nThe `active.clojure.match` namespace provides some syntactic sugar\nfor map matching around `core.match`.\n\n### Higher-order Functions\n\nThe `active.clojure.functions` namespace provides the same higher order\nfunctions that `clojure.core` does, but implemented via records and\n`IFn`, so that the returned \"functions\" are `=` if created with `=` arguments.\n\nThese can be very handy for using React-based libraries like [Reacl](https://github.com/active-group/reacl),\nwhich can optimize work based on the equality of values.\n\n### Monad\n\nAn example usage of the `active.clojure.monad` namespace can be found at https://github.com/active-group/active-clojure-monad-example\n\n### Applicative Validation\n\nThe `active.clojure.validation` namespace provides utilities for\napplicative data validation.  It is useful to create validation\nfunctions that collect all errors that occured (as opposed to finding\nonly specific or one error) in a purely functional way.\n\nThe main building-blocks are the `validate-*` functions and\n`validate`.\n\n#### Applicative Validation: Example\nAn idiomatic example, hiding the actual record constructor and\nexposing only a validated record constructor:\n\n```clojure\n(ns validation\n  (:require [active.clojure.record :as r]\n            [active.clojure.validation :as v]))\n\n(r/define-record-type Config\n  ^:private make-config config?\n  [host        config-host\n   port        config-port\n   mode        config-mode\n   admin-users config-admin-users])\n```\n\nHere, we define the record-type `Config`.  We want to have the\nfollowing rules:\n\n- The `host` is a non-empty string\n- The `port` must be an integer between 0 -- 65536\n- The `mode` must be one of `:dev`, `:test`, and `:prod`\n- the `admin-users` must be a sequence of non-empty strings.\n\nFirst, we define a validator for ports which is not already included\nin the library:\n\n```clojure\n(defn validate-port\n  \"Given a `candidate` value and an optional `label`, validates that\n  `candidate` is in [1 65535].\"\n  [candidate \u0026 [label]]\n  (v/make-validator candidate\n                    (fn [candidate]\n                      (and (\u003c 0 candidate)\n                           (\u003e 65536 candidate)))\n                    ::port\n                    label))\n```\n\n`make-validator` returns a function that checks if the candidate is\nvalid and returns either a `ValidationSuccess` or a\n`ValidationFailure`.  This can then be combined with other validators\nto create a validated constructor for `Config`:\n\n```clojure\n(defn create-config\n  \"Creates a validated [[Config]], wrapped in a 'ValidationSuccess'.  If\n  any arguments are invalid, returns a 'ValidationFailure' holding all\n  'ValidationError's.\"\n  [host port mode admin-users]\n  (v/validation make-config\n                (v/validate-non-empty-string host :host)\n                ;; NOTE: We could also check for pos-int in\n                ;; `validate-port`.  This is intended to show the\n                ;; `validate-all` combinator.\n                (v/validate-all [v/validate-pos-int validate-port] port :port)\n                (v/validate-one-of #{:dev :test :prod} mode :mode)\n                (v/sequence-of v/validate-non-empty-string admin-users :admin-users)))\n```\n\nWe will go through the parts of this expression one by one.\n\n- `(v/validation make-config \u003cvalidations\u003e)` means that, given all\n  `\u003cvalidations\u003e` are `ValidationSuccess`es, call the function\n  `make-config` with the validated candidate values.\n- `(v/validate-non-empty-string host :host)` uses\n  `validate-non-empty-string` from the validation library and checks\n  if `host` is a string and not empty.  If it fails, it will keep\n  `:host` as a label to refer back to the argument.  All labels are\n  optional, but it is a good idea to state a label if you want to map\n  back from error to cause.\n- `(v/validate-all [v/validate-pos-int validate-port] port :port)`\n  uses the `v/validate-all` combinator to say that 'the candidate must\n  satisfy all of the following validations, `v/validate-pos-int` and\n  `validate-port`'.  It will use both validators on the candidate and\n  combine both errors if there are any into one `ValidationFailure`.\n- `(v/validate-one-of #{:dev :test :prod} mode :mode)` validates that\n  `mode` is in the specified set of values.\n- `(v/sequence-of v/validate-non-empty-string admin-users\n  :admin-users`) also pretty much does what it says on the label: It\n  validates that `admin-users` is a sequence of values, each of which\n  satisfy the `validate-non-empty-string` validation.\n\nLets look at some results:\n\n```clojure\n;; Valid arguments, returns a ValidationSuccess holding the validated candidate.\n(create-config \"host\" 8888 :dev [\"user1\" \"user2\"])\n;; =\u003e #active.clojure.validation/ValidationSuccess{:candidate\n;;      #validation/Config{:host        \"host\"\n;;                         :port        8888\n;;                         :dev-mode?   true\n;;                         :admin-users [\"user1\" \"user\"2]}}\n```\n\nHopefully the most common case: All arguments are valid, therefore the\nwhole validation succeeds and returns the validated candidate value,\nwrapped in a `ValidationSuccess`.\n\n```clojure\n;; Every argument is invalid, returns a ValidationFailure with all ValidationErrors.\n(create-config \"\" -1 :staging [\"user1\" \"\"])\n;; =\u003e #active.clojure.validation/ValidationFailure{:errors\n;;      [#active.clojure.validation/ValidationError{:candidate \"\"\n;;                                                  :message   :active.clojure.validation/non-empty-string\n;;                                                  :label     :host}\n;;       #active.clojure.validation/ValidationError{:candidate -1\n;;                                                  :message   :active.clojure.validation/pos-int\n;;                                                  :label     :port}\n;;       #active.clojure.validation/ValidationError{:candidate -1\n;;                                                  :message   :validation/port\n;;                                                  :label     :port}\n;;       #active.clojure.validation/ValidationError{:candidate :staging\n;;                                                  :message   [:active.clojure.validation/one-of #{:prod :test :dev}]\n;;                                                  :label     :port}\n;;       #active.clojure.validation/ValidationError{:candidate \"\"\n;;                                                  :message   :active.clojure.validation/one-of #{:prod :test :dev}\n;;                                                  :label     [:admin-users 1]}]}\n```\n\nThe dire case in which each argument is invalid.  Note that the result\nis a `ValidationFailure` that contains a sequence of\n`ValidationError`s.  Each error tells us which candidate was causing\nthe error (`:candidate`), which validation was violated (`:message`)\nand gives us a `:label` to refer back to the cause of the error.  Also\nnote that the `-1` shows up twice.  This is to be expected, because it\nviolates both validations of our `validate-all` clause.\n\n```clojure\n;; Only some arguments are invalid\n(create-config \"\" 65537 :dev [\"user1\" \"\"])\n;; =\u003e #active.clojure.validation/ValidationFailure{:errors\n;;      [#active.clojure.validation/ValidationError{:candidate \"\"\n;;                                                  :message   :active.clojure.validation/non-empty-string\n;;                                                  :label     :host}\n;;       #active.clojure.validation/ValidationError{:candidate 65537\n;;                                                  :message   :validation/port\n;;                                                  :label     :port}\n;;       #active.clojure.validation/ValidationError{:candidate \"\"\n;;                                                  :message   :active.clojure.validation/one-of #{:prod :test :dev}\n;;                                                  :label     [:admin-users 1]}]}\n```\n\nThis case shows the result of only some validations failing.  Take\nnote of the `:port` validation again.  This time it is only one error.\nThis is because it satisfies the `validate-pos-int` validation but not\nour custom validation specifying the legal port range.\n\n## Development\n\n### Testing\n\nThe Clojure tests can be executed with `lein test`. For *auto-testing* the\nClojureScript code, we use\n[figwheel-main](https://github.com/bhauman/figwheel-main). In a terminal, run\n`lein fig`, which then starts a CLJS REPL. Opening\nhttp://localhost:9500/figwheel-extra-main/auto-testing in a browser window will\nthen run the tests and display the results. After every code change, it will\nautomatically reload and re-run the tests, notifying you via the browser of the\nresult.\n\n## License\n\nCopyright © 2014-2025 Active Group GmbH\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%2Factive-group%2Factive-clojure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factive-group%2Factive-clojure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factive-group%2Factive-clojure/lists"}