{"id":27194725,"url":"https://github.com/dtenny/clj-coll","last_synced_at":"2025-04-09T19:04:20.389Z","repository":{"id":286320571,"uuid":"961052552","full_name":"dtenny/clj-coll","owner":"dtenny","description":"Clojure collection and sequence APIs in Common Lisp, with optional Clojure collection syntax","archived":false,"fork":false,"pushed_at":"2025-04-05T17:10:37.000Z","size":197,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-05T18:22:30.448Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","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/dtenny.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":"2025-04-05T16:45:20.000Z","updated_at":"2025-04-05T17:10:41.000Z","dependencies_parsed_at":"2025-04-05T18:22:36.288Z","dependency_job_id":null,"html_url":"https://github.com/dtenny/clj-coll","commit_stats":null,"previous_names":["dtenny/clj-coll"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-coll","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-coll/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-coll/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-coll/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dtenny","download_url":"https://codeload.github.com/dtenny/clj-coll/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248094986,"owners_count":21046770,"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-04-09T19:02:29.240Z","updated_at":"2025-04-09T19:04:20.375Z","avatar_url":"https://github.com/dtenny.png","language":"Common Lisp","funding_links":[],"categories":["Recently Updated","Clojure"],"sub_categories":["[Apr 08, 2025](/content/2025/04/08/README.md)"],"readme":"# CLJ-COLL: looks like Clojure, tastes like Common Lisp!\n\nThis is a Common Lisp implementation of Clojure's APIs for collections,\nseqs, and lazy-seqs.  It provides immutable Cons, Queue, PersistentList,\ncapabilities as well as Vector, Set,\nand Map analogues built on [FSet](https://github.com/slburson/fset) (but\naccessed entirely via Clojure APIs).\n\n`CLJ-COLL` is intended to give a \"most naturally integrated\" experience of Clojure\nAPIs and immutable data structures within a Common Lisp environment, and to make\nCommon Lisp more approachable to Clojure programmers. If you're a developer\nwho regularly writes both Common Lisp and Clojure, this library is for you.\n\nThis is _not_ a Clojure implementation. There is no `def`, `defn`, `fn`,\nor Clojure-style destructuring. Similarly, there no\nClojure-compatible LOOP/RECUR support, use CL:LOOP or other favored\nCL iteration mechanism. Real FP programmers don't LOOP anyway :-)\nCLJ-COLL does provide a DOSEQ as well as the full range of Clojure compatible\nmap/reduce/transduce behaviors.\n\nIf your goal is to have a full-on Clojure language implementation implemented in\nCommon Lisp, consider [Cloture](https://github.com/ruricolist/cloture) or\nsome other tool, though frankly you may as well use [Clojure](https://clojure.org).\n\n## Contents\n\n* [Clojure APIs provided](#clojure-apis-provided)\n* [What else is in the box besides collection and seq APIs?](#what-else-is-in-the-box-besides-collection-and-seq-apis)\n* [Things that aren't in the box that you can obtain elsewhere](#things-that-arent-in-the-box-that-you-can-obtain-elsewhere)\n* [Usage](#usage)\n  - [Try it out: clj-coll-user package](#try-it-out-with-the-clj-coll-user-package)\n  - [Print hash-tables or other data structures like Clojure](#print-hash-tables-or-other-data-structures-like-clojure)\n  - [Enable read syntax for hashtables (with or without immutable maps)](#enable-read-syntax-for-hashtables-with-or-without-immutable-maps)\n  - [Selective CLJ-COLL access](#selective-clj-coll-access)\n  - [Recommended: The Whole Enchilada](#recommended-the-whole-enchilada)\n* [Running unit tests](#to-run-the-unit-tests)\n* [Readtable syntax support](#readtable-syntax-support)\n  - [Note on named-readtables and the REPL](#note-on-named-readtables-and-the-repl)\n* [Comparisons and equality](#comparisons-and-equality)\n  - [`defstruct` and `defclass` instance comparisons](#defstruct-and-defclass-instance-comparisons)\n  - [Use `EQUAL?`, not `=` for CLJ-COLL equivalence testing](#use-equal-not-for-clj-coll-equivalence-testing)\n* [Keywords and keyed collections as predicates](#keywords-and-keyed-collections-as-predicates)\n* [\"M functions\"](#m-functions)\n  - [The complete list of M functions](#the-complete-list-of-m-functions)\n  - [_Most_ M functions do not have (or need) transducer arities](#most-m-functions-do-not-have-or-need-transducer-arities)\n* [What works well, what doesn't](#what-works-well-what-doesnt)\n  - [Works well](#works-well)\n  - [A bit rough](#a-bit-rough)\n  - [Missing capabilities](#missing-capabilities)\n  - [Differences from Clojure (mostly non-API)](#differences-from-clojure)\n* [Notable semantics \u0026 cautions](#notable-semantics-cautions)\n  - [Mutating APIs](#mutating-apis)\n  - [APIS that reject CL:HASH-TABLEs](#clhash-table-rejecting-apis)\n  - [Syntax Caveat: don't quote](#evaluation-environmentresult-is-not-identical-to-clojure)\n* [Provided named readtables](#named-readtables-provided-by-clj-coll)\n* [Vector/set/map print representations](#print-representations-of-vectors-sets-and-maps)\n  - [Printing of mutable vs. immutable maps and lists](#printing-of-mutable-vs-immutable-maps,-and-immutable-lists)\n* [Emacs tips for brace-delimited form travel](#emacs-tips-for-brace-delimited-form-travel)\n  - [API differences](#api-differences-from-clojure)\n* [Lisp implementations tested](#tested-lisps)\n* [CLJ-COLL non-goals](#clj-coll-non-goals)\n* [Related projects and differences from CLJ-COLL](#related-projects-differences-from-clj-coll)\n* [Frequently asked questions](#frequently-asked-questions)\n* [Additional reading - including deferred APIs](#additional-clj-coll-readingnotes)\n* [Feedback welcome](#feedback welcome)\n\n## Clojure APIs provided\n\nAt the time of this writing, the first release of CLJ-COLL exports 257\nfunctions, of which 16 are [M functions](#m-functions).  There are a\nsmall handful of capability predicates and miscellaneous things which are\nnot in Clojure, but most of the functions are straight out of Clojure's\n`clojure.core` and `clojure.set` namespaces. \n\nThe [Clojure Cheatsheet](https://clojure.org/api/cheatsheet), which by the\nway has an _excellent_ [downloadable version](https://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html),\nis your friend here. It summarizes all the APIs grouped by\narea of functionality, offers popup doc strings, and click-through\ndescriptions with examples at [Clojuredocs.org](https://clojuredocs.org).\n\nNearly every Clojure 1.12 collection and sequence API is present in\nCLJ-COLL. There are just a few that aren't (and are documented in this\nREADME). (Note that at last glance the cheatsheet is missing 1.12 functionality).\n\nCLJ-COLL also exports a bunch of miscellaneous Clojure functions, e.g `inc` and\n`dec`, `odd?` and `even?` as well as higher order function helpers like\n`comp`, `juxt`, and so on.  They moslty just wrap Common Lisp functions that do the\nsame thing and are purely for your clojure-cognitive convenience, ensuring\nthat formal parameters match Clojure's APIs.\n\nIf you don't want to use the cheatsheet, refer to the CLJ-COLL package\nexport list (which is annotated and occasionally tabulated) in \n`package.lisp`.  Every exported function has a doc string.  And of course\nthere's the CL `apropos` function to help you find things.\n\n## What else is in the box besides collection and seq APIs?\n\n* Full interoperability with Common Lisp collection types CL:LIST,\n  CL:VECTOR (which implies strings), and CL:HASH-TABLE.  Clojure APIs like `doseq`\n  or `filter` will work with these collections as well as immutable collections.\n  \n  Multidimensional and specialized Common Lisp arrays are NOT\n  interoperable, though of course they're still available to you because\n  it's Common Lisp, yay!\n\n* All the core unsorted immutable collections. Lists, vectors, maps,\n  sets, and queues.\n\n* Lazy sequences and full `seq` support on all collections.\n\n* All of Clojure's core transducers as of the time of this writing.\n\n* Optional printing support for CL hash-tables and immutable collections so\n  collections will print similarly (if imperfectly) to Clojure's print style.\n\n* Optional read syntax via [named readtables](https://github.com/melisgl/named-readtables)\n  so you can type `{:a 1 :b 2}`, `#{1 2 3}`, and `[1 2 3]` to your heart's content.\n  Note that syntax doesn't extend to Clojure's way of treating commas as\n  whitespace, you can't use commas that way in Common Lisp.\n\n* A clojure-like equality/equivalence predicate `equal?`\n  (instead of Clojure's `=`), so you can compare vectors to lists to seqs\n  with wild abandon in those unit tests.\n\n* Some trivial Clojure functions to make sharing code back and forth\n  between Clojure and CL a bit easier, e.g. `inc` and `dec`.\n\n* Some trivial higher order function support, e.g. `comp`, `partial`,\n  `juxt`.\n\n* A set of corresponding [\"M Functions\"](#m-functions) which emulate lazy\n  Clojure APIs but always return eager and mutable CL collection types.\n\n### `queue` - a constructor function for queues\n\nWho can resist the elite practice of requiring clojure Queues to be\nconstructed by references to `clojure.lang.PersistentQueue/EMPTY` and a\nbunch of additional `conj` calls?\n\nWe can. Being as there's no java code that provides\n`clojure.lang.PersistentQueue/EMPTY` in Common Lisp, you can use either the\n`CLJ-COLL:*EMPTY-QUEUE*` constant to construct your queues as in Clojure, \nor you can use the `queue` function and save yourself a lot of typing.\n\n## Things that aren't in the box that you can obtain elsewhere\n\nIf you're reading this, you may also find these other Clojure functionality\npackages useful.  They are not required to use `CLJ-COLL`, but\nshould work well with it.  Each of the packages below were designed\nto closely adhere to Clojure semantics, though they predate CLJ-COLL\nand may return multiple values in one or two cases where Clojure would\nreturn persistent vectors.\n\n* [Clojure compatible arrow macros](https://github.com/dtenny/clj-arrows)\n  (which is bundled in the `:CLJ-COLL-USER` system provided alongside CLJ-COLL).\n* [Clojure concurrency APIs](https://github.com/dtenny/clj-con)\n* [Clojure compatible regular expression APIs](https://github.com/dtenny/clj-re)\n\n# Usage\n\n`CLJ-COLL` is available via Ultralisp or via [github](https://github.com/dtenny/clj-coll).\n\nIf you didn't get this via quickload using a quicklisp/ultralisp repo, add it to\nyour `~/quicklisp/localprojects/` directory and update or delete the\n`system-index.txt` file (so it will be re-built), and then you can quickload it.\n\n    (ql:quickload :clj-coll) ; to use the code\n\nThere are a number of ways you might choose to use the CLJ-COLL package.\n[Jump ahead to the recommended usage](#4-recommended-the-whole-enchilada) or\nuse bits of CLJ-COLL a la carte.\n\n## Try it out with the CLJ-USER package\n\nIf you just want to play around with CLJ-COLL, try the `CLJ-USER`\npackage (which is in the `CLJ-COLL-USER` _system_).\n\n    (ql:quickload :clj-coll-user)\n    (in-package :clj-user)\n    (named-readtables:in-readtable clj-coll:readtable) ; if you're not using Slime\n    {:a 1} ; =\u003e {:a 1} - congratualtions, you just created an immutable map\n\nNote that you must invoke the `in-readtable` if your repl interaction is a\nsimple terminal session.  Slime knows how to save you the trouble, perhaps\nSly does too.\n\nThe CLJ-USER package pulls in CLJ-COLL, and the clojure arrow macros so you\ncan start banging away at Clojure-like constructs.  Just remember to `#'`\nyour functions passed as arguments, and that you're using Common Lisp\nvariants of `let`, `cond`, and so on, which use parenthesis, not bracketed\nvector syntax, for their form syntax.  CLJ-COLL is only about collections\nand sequences, not so much Clojure's macros and special forms.\n\n## 1. Print hash-tables or other data structures like Clojure\n\nBy default CLJ-COLL doesn't mess with your print methods for CL or FSet\ndata types.  If you would like them to print more like Clojure, you can\nenable each types's printing individually or collectively as follows:\n\n    ;; Use zero or more of the following to suit your collection printing tastes\n    (enable-printing)        ; Enable pretty printing for maps/sets/vectors\n\n    (enable-map-printing)    ; Enable pretty printing for maps only\n    (enable-set-printing)    ; Enable pretty printing for sets only\n    (enable-vector-printing) ; Enable pretty printing for vectors only\n\n## 2. Enable read syntax for hashtables (with or without immutable maps)\n\nCLJ-COLL doesn't mess with the global read-table.  You can used \n[named readtables](https://github.com/melisgl/named-readtables) to\nindividually or collectively enable CLojure reader syntax, e.g. `{:a 1 :b\n2}` for hash tables. \n\n    ;; Use zero or more of the following to suit your (Clojure) syntax tastes\n    (named-readtables:in-readtable clj-coll:readtable)        ; For all set/vector/map syntax\n\n    (named-readtables:in-readtable clj-coll:map-readtable)    ; For map syntax only\n    (named-readtables:in-readtable clj-coll:vector-readtable) ; For vector syntax only\n    (named-readtables:in-readtable clj-coll:set-readtable)    ; For set syntax only\n\nBy default the hash-table syntax reader will create immutable maps.  If you\nwould rather forego immutable data types and have the reader syntax create\nCL:HASH-TABLE objects, simply set or bind `*DEFAULT-HASHMAP-CONSTRUCTOR*` \nto `CLJ-COLL:CL-HASH-MAP`.\n\nSee [Note on named-readtables and the REPL](#note-on-named-readtables-and-the-repl) \nof you use your REPL from a terminal instead of via Slime.\n\n## 3. Selective CLJ-COLL access\n\nTL;DR: This is doing it the hard way. See next section.\n\nCLJ-COLL shadows many common lisp symbols in order to provide clojure seq\nand lazyseq semantics for many of the APIs. If you want to take full\nadvantage of it in the easiest way, refer to the next section. However you\ndon't need to 'USE' all those shadowed symbols if you don't want to.\n\nAside from simply using package qualified references,\ne.g. `(CLJ-COLL:FILTER pred coll)`, you may wish to `:import` just the \"M\nFunctions\" and/or collection APIs that eschew the immutable and lazy\nbehaviors in favor non-lazy result, not-mutable results.\n\nThe [M functions](#m-functions) can be imported as:\n\n    (defpackage my-package\n      ... \u003cyour stuff\u003e ...\n      (import-from :clj-coll \n        :mbutlast :mconcat :mcycle :mdedupe :mdistinct :mdrop :mdrop-last :mdrop-while\n        :mfilter :mflatten :minterleave :minterpose :mjuxt :mkeep :mkeep-indexed\n        :mkeys :mmap :mmapcat :mpartition :mpartition-all :mpartition-by\n        :mremove :mrandom-sample :mrange :mrepeat :mrepeatedly :mreplace :mreverse\n        :mshuffle :msplit-at :msplit-with :mtake :mtake-last :mtake-while :mvals))\n\n\nThere are a lot of functions in the Clojure API that do not always\nreturn immutable data types and do not shadow CL package symbols, including:\n\n    (import-from :clj-coll\n      :any? :assoc-in :bounded-count :cl-conj :conj :contains? :count :count-while\n      :difference :disj :dissoc :distinct? :doseq :empty :empty? :every? \n      :frequencies :get-in :group-by :index :index-of :join :map-invert\n      :merge-with :not-any? :not-empty :not-every? :last-index-of :peek\n      :pop :postwalk :prewalk :postwalk-replace :prewalk-replace :project\n      :rand-nth :reduce-kv :rename :rename-keys :select :select-keys\n      :subset? :superset? :subvec :update :update-in :update-keys\n      :update-vals :walk\n\n      :coll? :collp :cons? :list? :map-entry? :map? :mapp :queue :queue?\n      :set? :string? :vector? :associative? :counted? :reversible? :seq?\n      :seqable? :sequential?\n\n      :cat :completing :deref :ensure-reduced :halt-when :unreduced\n      :reduced? into :transduce\n    \n      :doall :dorun :lazy-cat :lazy-seq :nthnext :nthrest :realized? :seq :rseq)\n\nHowever if you are using many of these you're very likely going to\nwant to pull in all the symbols, including those that shadow CL symbols.\nFor example, what is the point of importing CLJ-COLL:NEXT for seq traversal\nif you haven't imported CLJ-COLL:FIRST, which is pretty important to seq\nvalue access?\n\n## 4. [Recommended] The Whole Enchilada\n\nIf you're a fairly frequent Clojure programmer, this is the recommended package \nsetup for your package:\n\n    (defpackage :my-package\n      (:use :cl :clj-coll :clj-arrows)\n      (:shadowing-import-from :clj-coll \n       :assoc :cons :count :first :get :merge :nth :last :list :listp\n       :reduce :rest :second :set :vector :vectorp))\n\n    (in-package :my-package)\n    (named-readtables:in-readtable clj-coll:readtable) ;all syntax read assists\n    (enable-printing) ;all syntax print assists\n\nThe above has been done for you in the form of the `:CLJ-COLL-USER` ASDF\nsystem, which you can use with `quickload`. It defines a `CLJ-USER`\npackage, imports everything from CLJ-COLL, shadows the necessary CL package\nsymbols, sets up the named readtable with Clojure syntax, and printing to\nresemble Clojure's. So you can experiment a bit with it if you quickload\n`:CLJ-COLL-USER` and `(in-package :clj-user)`.\n\nWhen using a REPL in the above package, you'll be able to use all the APIs,\nall the set/map/vector/queue syntax, and all the APIs pretty much as you would in\nclojure, except that it handles all the CL collection types as well.\nSee `package.lisp` for a full set of symbols exported by CLJ-COLL.\nYou'll still need to `#'` your functions when using them as arguments\nthough, i.e. `(filter #'odd? coll)`, not `(filter odd? coll)`.  Tip: \nyou can run `(filter #'odd? coll)` in Clojure, and so copy some forms\ndirectly from CL to Clojure for comparison purposes.\n\nNote that named-readtables have package-local effect.  If you use\n`in-readtable` in :CL-USER to enable map syntax, that doesn't mean it will\nwork in another package unless and until you perform an `in-readtable` in\nthat package.\n\nFor more on reading and printing see [readtables](#named-readtables-provided-by-clj-coll) and\n[printing](#print-representations-of-vectors-sets-and-maps) sections later\nin this document.\n\n### A word on the Serapeum package\n\n[Serapeum](https://github.com/ruricolist/serapeum) is a very popular lisp\nutility collection which has many Clojure-inspired functions. There are a\nnumber of symbols it exports that collide with CLJ-COLL symbols, these are\nthe ones known at this time on which you will either need to shadow or\notherwise avoid duplicate imports if you wish to `:USE` both `:CLJ-COLL`\nand `:SERAPEUM`:\n\n    DROP, JUXT, RANGE, TAKE, CONCAT, NTHREST, DISTINCT, QUEUE, FREQUENCIES,\n    PARTIAL, FILTER, DROP-WHILE, FNIL, TAKE-WHILE, PARTITION.\n\nUse whichever suits your needs, bearing mind that Serapeum's namesakes\nmay perform the same function but will not operate on or return CLJ-COLL types.\nThat said, they may be quite a bit more efficient.\n\nIt is recommended you prefer `CLJ-COLL:FNIL` over Serapeum's, as the Serapeum\nversion has a known [bug](https://github.com/ruricolist/serapeum/issues/185).\n\n## To run the unit tests\n\n    ;; To run the tests\n    (ql:quickload :clj-coll-test)\n    (clj-coll-test::run-tests)\n\n# Readtable syntax support\n\nMap, set, and vector syntax is provided via\n[named readtables](https://github.com/melisgl/named-readtables).  For\nexample:\n\n* Maps: `{:a 1 :b 2}` \n* Sets: `#{1 2 3}`\n* Vectors: `[1 2 3]`\n\nThe use of any syntax is optional, you can also create your own read-tables\nwith only select syntax, e.g. vectors without sets, via single-function\nreadtables that may be used as mixins, descrbed further below.\n\nEach collection type has a special variable which is used to create\ncollections of the appropriate type, but which you can rebind\nto create other types of collections, including mutable Common Lisp\ncollections. The default behavior is to create immutable data structures\nwhen the syntax assists are used.\n\nChoices of readtable syntax support does not in any way affect the printed\nrepresentations of objects.  Print representations are discused later.\n\n## Note on named-readtables and the REPL\n\nSomething to be aware of if you use the lisp repl without Slime (and\nperhaps Sly, unknown to the author).\n\nSay you have a lisp file/package you've loaded which invokes\n`(named-readtables:in-readtable clj-coll:readtable)`\nin the context of some package X (such as the\n`CLJ-COLL-TEST` package).\n\nIf you are using the Slime repl and you execute `(in-package :x)`, Slime\nwill nicely ensure that the package's `in-readtable` context is in effect for your\nrepl so that you can invoke the modified syntax.\n\nHowever if you're using a lisp repl from a terminal, none of the vendor\nspecific repls tested from a terminal did that. So this package\u003c-\u003ereadtable\nobservance for repls is a Slime benefit. \n\n# Comparisons and equality\n\nCLJ-COLL relies on FSet to provide the implementations of immutable sets, maps, and\nvectors.  CLJ-COLL provides the implementations for immutable lists and\nqueues, and all seqs and lazy-seqs. Everything else is Common Lisp\n\nTL;DR: Beware mixing mutable and immutable types, particularly hash-tables\nof either sort, and know that associative things with keys tend to use\nCL:EQUAL-ish semantics.\n\nClojure relies on a number of things in its various equality and\ncomparison predicates, starting with the java Object.equals() method\nwhich is used in part when you use Clojure's `=` predicate, as well as\nall the numerical tests such as `== \u003c \u003e \u003c= \u003e=`.\n \nPerhaps unknown to casual Clojure programmers is Clojure's `equiv` logic.\nIt is `equiv` that let's you compare a sequence represented by vectors\nwith a sequence represented by lists, for example.  While Common Lisp's\n`equal` and `equalp` will do certain flavors of structural equivalence\nbetween CL collections, they won't do anything for user packages like the\nimmutable classes used by CLJ-COLL.\n\nCLJ-COLL attemps to approximate the equivalence logic of Clojure's `=`.\nCollections of different types which have similar sequential or\nassociative behaviors and similar content will compare equal.\nE.g. CL:VECTOR will compare in a sane way with an immutable vector or\nelements of a CLJ-COLL/Clojure 'seq'. This is mostly\nstraightforward (though not terribly performant) but there are a few caveats.\n\nEquality tests have the following limitations at this time:\n\n* Most CLJ-COLL APIs _that do not involve hash-table and set key\n  comparisons_ will use CLJ-COLL:EQUAL? for equality, which is similar to\n  Clojure. Otherwise key comparison tends to resemble CL:EQUAL.\n* CL:HASH-TABLE objects instantiated CLJ-COLL APIs will specify CL:EQUAL as\n  the equality predicate.\n* FSET:MAP keys are only compared with FSET:EQUAL?, which is more liberal\n  than CL:EQUAL, but not as liberal as CLJ-COLL:EQUAL?. Notably, it will\n  not do structural equivalence comparisons of CL:HASH-TABLE objects with\n  FSET:MAP types (so don't use a CL hash-table as a key in an FSet map).\n* FSET:SET keys are only compared with FSET:EQUAL?\n* FSET:EQUAL?, and this is important for FSET:SET and FSET:MAP collections\n  only uses EQ on CL:HASHTABLE objects.  This basically precludes\n  use of CL:HASH-TABLE inputs on the clojure.set namespace functions\n  which look for \"rels\" (sets of maps).  CLJ-COLL will signal an error\n  if mutable inputs are used for incompatible operations on \"rels\".\n\nThe key restrictions apply mostly when you're populating collections.  If\nyou're comparing one collection/seq to another with CLJ-COLL:EQUAL?,\nCLJ-COLL:EQUAL? is used for all types except those provided by FSet\n(immutable maps, sets, and vectors).\n\nHere are rules of thumb about what is being compared:\n\n* FSET:EQUAL? knows how to compare fset collections and contents of fset\n  collections, but devolves to something approximating CL:EQUAL for\n  anything that isn't an FSET data structure.\n\n* CLJ-COLL:EQUAL? knows how to compare based on structural equivalence of\n  collections, but you can't use this for hashtable/map or set keys right now.\n\n* CL structure-object and standard-object instances\n  be compared with EQUAL in both FSET:EQUAL?\n  and CLJ-COLL:EQUAL? (meaning it's really an EQ test), though you can\n  widen the CLJ-COLL:EQUAL? logic for these objects through some special\n  variables described in the next section.\n\nThe effect of this is that if you use only scalar or CLJ-COLL immutable\ntypes, then CLJ-COLL key semantics are close to Clojure's.\n\nIf you want mutable hash tables that accept `CLJ-COLL::EQUAL?` then more work\nneeds to be done, such as using https://github.com/metawilm/cl-custom-hash-table\n(`:cl-custom-hash-table` in quicklisp, and the basis for `:generic-cl.map`).\n\nYour choice of lisp implementation may also allow you to use\n`CLJ-COLL:EQUAL?` as a key equality predicate (SBCL has such support),\nhowever CLJ-COLL does not try to make use of this, that's up to\nyou. Feedback / discussion is welcome, it would be nice to throw off the\nshackles of `CL:HASH-TABLE` test limitations. The longer range plan is\n(perhaps) to\nuse something like\n[cl-custom-hash-table](https://github.com/metawilm/cl-custom-hash-table)\nfor portable and CLJ-COLL:EQUAL? capabilities.\n\nFor an example of map/set key comparison behaviors, look at the `contains?`\nunit test in `clj-coll-test.lisp`.\n\nNote for CLJ-COLL purposes strings are treated as scalar\nvalues, with cautions about mutating them. Fset treats them similarly,\naiding in Clojure-like semantics.\n\nImplementation note: FSET does not allow specification of user-supplied\nequality predicates for its MAP and SET implementations at the time of this writing,\nif did we could supply the more liberal `CLJ-COLL:EQUAL?` predicate to it for\nkey comparisons.\n\n## `defstruct` and `defclass` instance comparisons\n\nTL;DR: `defstruct` and `defclass` instances are compared by identity, like\nClojure, but CLJ-CON provides you a number of probably-bad-idea options to\nchange that if you want to.  *TBD*: whether they're worth the added machine\ninstructions they require.\n\nWhile Clojure and CLJ-COLL compare maps by value (of content), Clojure a\nbit quirky when it comes to `defrecord` types.\n\nIf you compare a map to a Clojure `defrecord` or `deftype` instances, the\ntest is an identity test.  Both `defrecord` and `deftype` result in new\ntypes, and instances of those types result in equality tests via identity.\n\nClojure APIs generally treat defrecord instances as if they were maps. For\nexample you can `assoc` and `dissoc` it like a map. Enter the quirks for\nrecord types.  If you `assoc` a new key that wasn't in the `defrecord`\ndeclaration, you will get back a new instance of the record type with the\nadded key.  However if you `dissoc` a key that was in the `defrecord`\ndeclaration, you get back a map that is not of the record type, and will\ncompare by value with other maps instead of only by identity.\n\nWe _could_ imagine Common Lisp `defstruct` to be like `defrecord` if we\nwanted to, we could further imagine `defclass` to be an analogy to\n`deftype`. But we don't, though it might be interesting to let `assoc` and\n`dissoc` work on Common Lisp object instances (again, we don't for now,\nthough that could be really useful for stylistic functional programming reasons).\nWe could also imagine objects to be comparable to maps \u0026 hash-tables, but we don't for now.\n\nCLJ-COLL allows you to extend the manner in which equality is computed on\nstruct/class isntances as follows (noting that such behavior is not at all \nlike Clojure):\n\n1. Bind `CLJ-COLL:*COMPARE-OBJECTS-BY-VALUE*` to true, in which case the\n   behavior when comparing `defstruct` and `defclass` instances\n   with _other instances of identical type_ is by comparisons based on\n   the values and/or boundness (e.g. `slot-boundp`) of all slots in the instance.\n\n2. Specify function designators for `CLJ-COLL:*STANDARD-OBJECT-EQUALITY-FN*`\n   to compare standard-object instances, or\n   `CLJ-COLL:*STRUCTURE-OBJECT-EQUALITY-FN*` for structure-object instances.\n   These variables default to NIL, meaning use the default `CLJ-COLL::EQUIV?`\n   comparison semantics (which defaults to `EQ` unless option 1 above is used).\n\n3. define CLJ-COLL::EQUIV? methods on the class or structure types you care\n   about, or redefine them for STANDARD-OBJECT and STRUCTURE-OBJECT.\n   Note that doing this will likely break the CLJ-COLL-TEST unit tests, and\n   any other dependency on default behavior by other dependencies you may\n   have loaded using CLJ-COLL (an unlikely situation for most people).\n\n## Use `EQUAL?`, not `=` for CLJ-COLL equivalence testing\n\nBasically, where Clojure would use `=`, you should use `CLJ-COLL::EQUAL?`\nif you want Clojure-esque equality semantics. Redefining an an `=` method\nwas just one step too many for the author's taste, which prefers to let\nsleeping `=`'s lie and provide Common Lisp semantics.  The same logic is\nwhy we didn't attempt to define `CLJ-COLL::EQUAL`, it carries too much\nCommon Lisp baggage. Granted this is inconsistent with all the other\nmethods shadowed by CLJ-COLL, it was a matter of degree and the particularly sensitive\nsemantics, for example, we didn't want anybody to think they could specify\na `CLJ-COLL::EQUAL` predicate as a Common Lisp hash table test.\n\n# Keywords and keyed collections as predicates\n\n## Description\n\nUnlike Common Lisp, Clojure lets you use keywords, sets, and maps as\nfunctions, i.e. in the function position of an sexp.  Most of them allow\n'not-found' arguments too, except for use of symbols.\n\n   Keywords: (:a x)         == (get x :a)\n             (:a x :no)     == (get x :a :no)\n   Maps:     ({:a 1} x)     == (get x :a)\n             ({:a 1} x :no) == (get x :a :no)\n   Sets:     (#{:a} x)      == (get x :a)\n             (#{:a} x :no)  == (get x :a :no)\n   Symbols   ('a x)         == (get x 'a)    ;*NO* not-found parameter, can't use 'nil\n\nThis is not something you're going to be able to do in Common Lisp, though\nin some lisps you could set the symbol-function of symbols in the keyword\npackage, but it isn't portable.\n\nWe _could_ make life a bit more clojure-like in some cases by automatically\nturning keywords, maps, and sets into predicates on all clojure APIs that\naccept predicates. **BUT THIS HAS NOT BEEN DONE**\n\nIf we did that, then you could could invoke CLJ-COLL APIs like this example:\n\n    (filter #{:a :b} '(:c :b)) =\u003e (:b)\n\nVery clojure-like indeed.\n\nWe would transform the data types and pass on the appropriate function.\nSo a set `s` passed to `filter` would be transformed as\n\n    (let ((f (collection-lookup-function #{:a :b} :none)))\n     ... use F ...\n\n where F is approximated as\n\n   (lambda (item) (get #{:a b} item :none))\n\nNon-keyword symbols would not be usable as predicates, in part\nbecause symbols already represent function-designators and because\nthey only work in clojure in the function position which we can't do\nin CL. \n\nRight now there is a funciton `CLJ-COLL:COLL-FUN` which does the datatype\nto predicate conversions if you would like to use it.  However this has not\nbeen incorporated in all the APIs for performance sanity reasons. It was\nfelt that if every API that takes a predicate or function has to check for\nconversion with `COLL-FUN` there would be too many checks.  Perhaps this\ncan be automated (in upwardly compatible fashion) in the future but for now\nit needs a bit more thought.\n\n# \"M Functions\"\n\nFunctional programming and immutable data structurs are fine until they\naren't. Maybe you want to CL:APPLY a function to an actual bona fide\nCL:LIST, or `loop for x in (some-funciton-returning-a-cl:list)` because you\nlove CL:LOOP. Or maybe the immutable data structures are just too slow for\na particular problem.\n\nWhen you want Clojure API functionality but want a Common Lisp collection\nfrom it, use an M function, which will be named the same as the Clojure\nfunction except for an 'm' prefix.\n\nThe M functions generally have the same argument signatures as their\nCLojure API counterparts except that:\n\n- Most do not have transducer arities.\n- Most accept a optional arguments that let you specify if you want a\n  CL:LIST or CL:VECTOR result, and generally default to CL:LIST return values.\n\nIn order to give CL:SEQUENCE typed returns (which encompasses CL:LIST and\nCL:VECTOR), all M functions are of necessity _eager_ functions that will\nprocess all values in the seqs or collections they receive as input, so be\ncareful not to pass any infinite lazy sequences!\n\n## M functions are sometimes more efficient\n\nM functions try to be more efficient than their lazy counterparts. \n\nOne of the ways they do this is through structure sharing, where a CL:LIST\nor CL:VECTOR input may share structure with the M function return value. Of\ncourse immutable data structures try to do this too - with safer effects,\nbut sometimes fail for algorithmic reasons.\n\nM functions which perform structure sharing on CL types always document the\nbehavior in the docstring.\n\nAnother way M functions may try to reduce consing is through the use of\niteration techniques that do not cons. ArraySeqs are notoriously consing\nimmutable seq abstractions.  Some of the M functions endeavor to be smarter\nabout it, since they are relieved of the obligation to return specific things like\n\"realized lazy sequences\" (part of the `partition` Clojure contract), for example.\n\n## Which Clojure APIs have M functions?\n\nThe criteria for whether or not to have an M version of a Clojure API\nfunction is usually one of these things:\n\n1. The API would return a lazy sequence.\n2. The API would return an immutable result regardless of the input.\n\nM functions try to offer an alternative to Clojure semantics such that the\nresulting data is something that be processed by standard Common Lisp APIs.\n\n### The complete list of M functions\n\nHere are all of the M functions returning CL collection types.\n\nThe \"NoVec Reason\" is the rationale for not having a CL:VECTOR alternative.\n\n\"LAZY\" means there just wasn't enough motivation to add what may be a\nuseful CL:VECTOR alternative for the first release.\n\n\"OKAY\" means it the value proposition of a CL:VECTOR option seemed potentially dubious,\nthough you may gather from those labelled \"OKAY?\" that more contemplation is in order.\n\n    M-function      Default   CL:VECTOR  NoVec   Comments\n                    Return    option?    Reason\n\n    mbutlast        CL:LIST   YES \n    mconcat         CL:LIST   YES\n    mcycle          CL:LIST   NO         OKAY?\n    mdedupe         CL:LIST   YES\n    mdistinct       CL:LIST   YES\n    mdrop           CL:LIST   YES                Also has :BEST result-type option\n    mdrop-last      CL:LIST   YES\n    mdrop-while     CL:LIST   YES\n    mfilter         CL:LIST   NO         OKAY\n    mflatten        CL:LIST   NO         LAZY\n    minterleave     CL:LIST   NO         LAZY\n    minterpose      CL:LIST   NO         LAZY\n    mjuxt           CL:LIST   NO         OKAY?   JUXT produces immutable vectors.\n    mkeep           CL:LIST   NO         OKAY\n    mkeep-indexed   CL:LIST   NO         OKAY\n    mkeys           CL:LIST   YES\n    mmap            CL:LIST   YES                Optimized for single-collection use\n    mmapcat         CL:LIST   YES\n    mpartition      CL:LIST   YES                Uses KWARGS for _two_ result-type specs.\n    mpartition-all  CL:LIST   YES                Uses KWARGS for _two_ result-type specs.\n    mpartition-by   CL:LIST   NO         OKAY?   Two potential result-types like `mpartition`\n    mremove         CL:LIST   NO         OKAY\n    mrandom-sample  CL:LIST   NO         OKAY?\n    mrange          CL:LIST   YES\n    mrepeat         CL:LIST   NO         OKAY\n    mrepeatedly     CL:LIST   NO         OKAY\n    mreplace        \u003cvaries\u003e  MAYBE      OKAY    `mreplace` is quirky because `replace` is quirky\n    mreverse        CL:LIST   YES\n    mshuffle        CL:VECTOR                    Matches `shuffle` semantics, no CL:LIST return\n    msplit-at       CL:LIST   YES                Uses KWARGS for _two_ result-type specs.\n    msplit-with     CL:LIST   YES                Uses KWARGS for _two_ result-type specs.\n    mtake           CL:LIST   YES\n    mtake-last      CL:LIST   YES\n    mtake-while     CL:LIST   YES\n    mvals           CL:LIST   YES\n\n## _Most_ M functions do not have (or need) transducer arities\n\nM functions generally take the same inputs as their Clojure namesakes and\nproduce mutable Common Lisp collections instead of immutable collections.\nYou can call them with all the usual CLJ-COLL supported collections\n(mutable and immutable collections, and [lazy]seqs on them), you'll just\nget a CL collection back instead of a lazy-seq or other immutable\ncollection type.\n\nM functions generally do not benefit from transducer arities, you can turn\nany transducer into one that returns a mutable collection by using an\nappropriately mutable initial value (e.g. `(cl-vector)`) and `cl-conj` as\nthe reducing function instead of `conj`.  `cl-conj` creates CL:LIST and\nCL:VECTOR where `conj` would create persistent lists and vectors.\n\nHowever there are some APIs for which this isn't enough to get a fully\nmutable set of CL data types.  For example, the `partition`,\n`partition-by`, and `partition-all` APIs do not allow the caller to specify\nthe type of collection used for each partition in the result. Transducer\nparameters can influence the result type of `transduce`, but not the result\ntype of the partitions.\n\nM functions such as MPARTITION-ALL let you obtain common lisp collections for\nresults, (which you _can_ do with transducers) and common lisp collections\nfor partition values (which you _cannot_ do with transducers), and\nsometimes offer transducer arities as well.\n\n# What works well, what doesn't\n\nComparing CLJ-COLL to the Clojure collections APIs.\n\n## Works well\n\n* The API, still the same friendly API you know, extended to embrace Common\n  Lisp data structures (lists, arrays, hash-tables).\n* Immutable data structures too, yay!\n* Seqs, lazyseqs, all seamless like in Clojure.\n* Clojure collection syntax (`{}`, `#{}`, `[]`).\n\n## A bit rough\n\n* Inconsistent Clojure equality semantics, documented\n  [below](#comparisons-and-equality).  This could be improved in future\n  releases, it's just a SMOP (with classic connotations), though\n  for native CL:HASH-TABLE improvements depend on your lisp implementation.\n\n* Immutable data structure performance may not compare well to\n  Clojure's in the current release for data structure intensive code. The\n  current immutable data structures are not (necessarily) optimized for\n  Clojure's use cases, vector additions in particular.\n  \n* There is no chunking of lazy sequences, and no intention of ever trying\n  to make them faster with chunking. They have their uses, but you must weigh whether\n  the cost justifies their use. Anecdotally, lazy seqs are very fast as is\n  in CL.\n\n## Missing capabilities\n\n* 'transient' capabilities on immutable data structures.\n* Clojure equality/equivalence semantics for CL:HASH-TABLE, and FSET-assisted\n  implementations of immutable sets/vectors/maps.\n* Sorted collections (sorted-set, sorted-map).  While you may observe some sorted behaviors\n  in collections returned by hash-set or hash-map, do not count on them. CLJ-COLL\n  doesn't support them yet.\n* Thread-safe stateful transducers, although note that Clojure's claims of\n  thread-safe transducers depend on what you consider \"safe\".  Simply using\n  volatiles for state (as Clojure does) does not mean that Clojure's\n  transducers will behave as expected if used by concurrent threads.\n* No interfaces (CLOS mixins in this case) like ISeq. If you want to add\n  more collections, it is done via generic functions. Seriously, do you\n  really want an IDrop mixin in Common Lisp? Interfaces are for broken-ass\n  OOP systems that aren't CLOS ;-)  That said, we may eventually need some interfaces\n  for `iteration`, `eduction`, and a couple of other APIs, as mentioned previously.\n\n## Differences from Clojure\n\n* `=` is `CL:=`, `CLJ-COLL:EQUAL?` is its replacement.\n* `()` is a an empty persistent list in Clojure, and is not `nil?`. However\n  `()` in Common Lisp *is* `nil?`, and list syntax will result in standard mutable CL lists.\n* Keyed collection equality semantics differ as described in the section on\n  [Comparisons and equality](comparisons-and-equality).\n  Otherwise CLojure structural equality semantics are very\n  similar between Clojure's `=` and CLJ-COLL's `EQUAL?`.\n* Clojure syntax for vectors/sets/maps behaves differently in _quoted_ contexts,\n  described in more detail\n  [here](evaluation-environment-result-is-not-identical-to-clojure).\n* In Clojure you can `apply` functions to clojure collections in the\n  trailing argument position, whereas `cl:apply` requires a `cl:list`.\n  At this time, CLJ-COLL supplies a `clj-apply` function do emulate\n  Clojure's `apply` instead of shadowing `cl:apply`. (See `clj-apply` docstring\n  for reasoning). Feedback is welcome.\n* Keywords and keyed collections as predicates aren't supported as\n  syntactic predicates like Clojure does. However the `coll-fun` function\n  can be used to turn keywords and collections into equivalent predicates you can \n  pass as functions.\n* There are no Java libraries, or java interop to them.  Methods like\n  `.indexOf` and `.lastIndexOf` instead have CLJ-COLL namesakes of\n  `index-of` and `last-index-of`.\n  \nAll documentation strings are careful to note any differences from Clojure\n(to a point, they're not a substitute for reading this README file).\n\n# Notable semantics \u0026 cautions \n\nGeneral rules of thumb:\n\n* When you call Clojure APIs and pass in CLJ-COLL immutable data\n  structures, the semantics of the function should be 100% Clojure, subject\n  to caveats such as that there are no Clojure/java type hierarchies.\n\n* Mutable In, Mutable Out, for \"collection\" functions\n\n  This applies mostly to \"collection\" APIs that are intended to operate\n  on a collection vs a seq. If you pass a mutable collection in, you'll\n  likely get a mutable collection out.  Unlike the \"sequence\" APIs which\n  always return some type of 'seq', whether it's a lazy-seq, an ArraySeq,\n  or what have you.\n\n* Mutation\n\n  If a Clojure API logically changes a colleciton/seq, and if you pass a\n  mutable input to that function, your mutable collection may be changed.\n  See [Mutating APIs](#mutating-apis)\n  for a complete and blissfully short list things that mutate.\n  \n  Doc strings are also careful to say whether a given function mutates.\n\n  For some boring discussion/rationales/design-decisions on mutation in the CLJ-COLL API\n  see [Mutable data, destructive functions, API conventions](./MIMO-Quandaries.md).\n\n* If you put mutable elements into immutable collections, they remain\n  mutable but you should avoid such mutations, they will likely cause\n  troublesome behavior.\n\n  E.g. changing a mutable key (e.g. a string) in an immutable map \"is an\n  error\", nothing good will come of it, but CLJ-COLL isn't going to detect\n  it and give you a rational error (most of the time, there are _some_\n  safeguards that detect mutations that affect seq behaviors).\n\n* When CL hash-tables are created by `cl-hash-map` element equality is\n  `cl:equal` until such time as we can provide maps that support `equal?`.\n  Fset sets and maps use `FSET:EQUAL?` which is similar to `CL:EQUAL`.\n  If you call `make-hash-table` yourself, then equality is up to you.\n\n* On thread safety: immutable data structures and seqs should be thread-safe, but thus\n  far no intensive CLJ-COLL testing has been done to validate that.\n  \n  Mutable data strutures have no thread-safety guarantees, you'll have to\n  add concurrency controls to them if it matters.\n\n* CLJ-COLL does not detect cyclic data structures for any operation,\n  including EQUAL?.  The pretty printer _may_ catch them, it hasn't been tested.\n\n* Stateful transducers are not thread-safe. Clojure's claim to be, but it\n  would depend on your definition of thread safefy.  Just because they\n  declare state using Java's `volatile` doesn't mean they will provide the\n  desired semantics.  CLJ-COLL makes no pretenses, stateful transducers\n  should not be shared across threads.\n\n* Dotted pairs are dotted pairs, and nothing else.\n  They aren't MapEntry representations. They aren't lists.\n  They aren't vectors.  They will not meaningfully compare with anything\n  except another dotted pair.  There is no CLJ-COLL API that will\n  give you a dotted pair as output except for where they were input\n  to the underlying collection.\n\n* Many APIs will accept things which conform to java MapEntry\n  semantics, which is to say that they represent a key/value pair.\n  Such pairs may be expressed as as any 2 element list or vector \n  (mutable or immutable).  We are more liberal here than Clojure\n  which does not allow lists to represent mapentries.\n\n  CLJ-COLL APIs that return logical map-entry pairs will return cl:list\n  values if the underlying hash-table was mutable, and persistent vectors\n  if the source map was immutable.\n\n## Mutating APIs\n\nThe core APIs that mutate (mutable) inputs, and on which other 'changing'\nAPIs are built are are as follows:\n\n- ASSOC\n- CONJ\n- DISSOC (only on cl:hash-table)\n- POP    (only on cl:vector)\n- RENAME-KEYS\n- MREPLACE (only if the input is a CL:LIST or CL:VECTOR)\n\nEach of the above check `*MUTATION-CONSIDERED-HARMFUL*` and act\naccordingly. It is exported, feel free to bind it if you are debugging\nunexpected mutations, but running with it always set is not a supported activity.\nSee the variable docstring for more details.  Also note that if you pass\nmutable collections in such that they become keys or other components of\nimmutable collections, and you change them after the fact, APIs will misbehave.\nThat's on you :-)\n\nOther mutating APIs defined in terms of the above are:\n\n- ASSOC-IN\n- MERGE, MERGE-WITH\n- RENAME\n- REPLACE (only if the input is a CL:VECTOR)\n- UPDATE, UPDATE-IN\n\nSome of the APIs you'd think might be updating your mutable collections,\nsuch as `update-keys`, `update-vals`, `dedupe`, and so on, do not actually\nmodify the collections they receive.  This is because they tend to build up\nnew collections (regardless of whether the inputs were mutable or\nimmutable).  This is also true of nearly all sequence APIs.  So the list of\nmutating functions above is fairly short.\n\n## Mutable vs immutable map example\n\nHere we show the same APIs operating on immutable and mutable maps:\n\n    (let ((m {:a 1}))\n      (print (clj-coll:assoc m :b 2))\n      m)\n    {:b 2 :a 1}    ;printed structure is not EQ m\n    =\u003e {:a 1}      ;m is unchanged\n\n    (let ((m (serapeium:dict :a 1))) ; mutable Common Lisp hash table, EQUAL test\n      (print (clj-coll:assoc m :b 2))\n      m)\n    {:b 2 :a 1}    ;printed structure was actually m, not new map\n    =\u003e {:b 2 :a 1} ;m has mutated\n\n## CL:HASH-TABLE rejecting APIs\n\nSome of the `clojure.set` APIs will reject the presence of CL:HASH-TABLE\nelements in so-called `rels`, as documented and restricted by the `index`\nand `join` APIs.  This is because CL:HASH-TABLE membership in keyed FSet\ncollections are tested with `EQ`, which is pretty much a total fail for\nusing CL:HASH-TABLE objects as collection members in certain Clojure APIs.\n\nRight now the 'no mutable hashtables in rels' restriction is enforced by a\nstructure traversing function called `require-immutable-rel`.  Hopefully\nthis is a temporary restriction, but it may require using home grown persistent\nmaps and sets (which would be a win in general so we can support\nCLJ-COLL:EQUAL? in key semantics).\n\n## `{}` `[]` `#{}` evaluation environment/result is not identical to Clojure\n\nTL;DR: You can skip this, just don't `QUOTE` (`'`) collections using reader syntax.\n\nIt is a great boon to us Common Lispers that the formulators of the ANSI Common Lisp\nspecification provided user-definable readtable interfaces. These\ncapabilities let us formulate the Clojure map/set/vector\nreadtable syntax in a portable, standard-compliant way.\n\nThere are some limitations. For example you probably can't embed any of\n`{}[]` characters in your symbol names if you're using the modified\nread-tables. We can live with that (or avoid using those read-tables, since\nnamed-readtables are very flexible, and _optional_, in their use.) However\nthere are some things about the Common Lisp environment that differ from\nthe Clojure model and and the syntax-assists for sets/vectors/maps are not\nidentical in all use-cases.\n\nTL;DR: _quoted_ forms with embedded syntax won't have a chance to evaluate\nthe result of the reader macro, leaving you an unexpected and unevaluated S-EXP\nwhere you expected a set/vector/hashtable.\n\nThere are two main implementation choices available to use to implement\nthe syntax reader macros for `{}`, `[]`, and `#{}`:\n\n1. As a macro, returning an expansion to be evaluated after reading.\n2. As construct evaluated at read-time to produce an object.\n\n### Macro Expansion Downside\n\nIf we opt for the first behavior, a macro expansion, then there's this problem.\n\nIn Clojure you can write:\n\n    '(1 2 3 {:e 4})\n\nAnd the resulting list will have a map as its last value.  I.e. \n\n    (mapv type '(1 2 3 {:e 4}))\n    =\u003e [java.lang.Long java.lang.Long java.lang.Long clojure.lang.PersistentArrayMap]\n\nHowever if the reader macro returns `(FUNCALL *DEFAULT-HASHMAP-CONSTRUCTOR* :E 4)`\nthen the result of the expression read with the syntax in a quoted\nenvironment will not work like Clojure:\n\n    '(1 2 3 {:e 4})\n    =\u003e (1 2 3 (FUNCALL *DEFAULT-HASHMAP-CONSTRUCTOR* :E 4))\n\nTo get the Clojure effect you need to use `(list 1 2 3 {:e 4})` \ninstead of a quoted literal.\n\n    (list 1 2 3 {:e 4}))\n    =\u003e (1 2 3 {:e 4})\n\n### Evaluated read-time construct problems\n\nThe good: if the reader processes the map creation at read time, then\nliterals like '(1 2 3 {:e 4}) will return the expected map in the resulting\nlist.\n\n    '(1 2 3 {:e 4}))  ;`list` not required\n    =\u003e (1 2 3 {:e 4})\n    \nThe bad: evaluating the expression at read-time is problematic in that the code\nyou're reading may not have its environment sufficiently populated to do\nuseful read-time processing.\n\nAnything you want to evaluate at read-time has to be defined at\nread-time.  Mostly this means `*DEFAULT-HASHMAP-CONSTRUCTOR*`, and perhaps \nother constructs. So this won't work:\n\n    (type-of (fourth (let ((*DEFAULT-HASHMAP-CONSTRUCTOR* 'serapeum:dict)) '(1 2 3 {:e 4}))))\n    =\u003e CLJ-COLL::\u003cimmutable-map-type\u003e\n\nWhich isn't what we wanted at all, it should have been a CL:HASH-TABLE, but\nthe reader hasn't seen the binding of `*DEFAULT-HASHMAP-CONSTRUCTOR*` at read-time.\n\nNote that read-time evaluation is also not what clojure is doing either.  For example\n\n    '(1 2 3 {:a (make-foo)})\n    =\u003e (1 2 3 {:a (make-foo)})\n\nWhere the last value of that quoted list is\nclojure.lang.PersistentArrayMap but its subexpressions were not evaluated.\n\nPerhaps there's a way to do this, but it seems doubtful.  Clojure's\nread-time environment is _the_ environment.  When literals are processed,\neverything read before that is available in the environment. There is no separate\nenvironment for compilation vs execution.  Of course that's also why you\nhave to be careful what you initialize in Clojure variables, but that's a\nstory for another day.\n\n### Resolution: treat syntax like `{}` as macros, `{}` in quoted contexts will not work.\n\nThe current implementation assumes reader macro expansions will be evaluated.\nIf you need to put anything that isn't self-evaluating into your\nsyntax-assisted collection creations, make sure it isn't in a quoted\ncontext, you need the syntax expression to be evaluated.\n\nBad: `'(1 2 3 {:a 1})` \nGood: `(list 1 2 3 {:a 1})`\n\nIn writing the CLJ-COLL tests this has not been particularly inconvenient.\n\n# Named readtables provided by CLJ-COLL\n\nNamed readtables are terrific things. Among their features are the ability\nto merge read-tables, mixin-style, into new or existing readtables. For\nexample you could define a read-table with the map and vector syntax, but\nwithout set syntax.\n\n`CLJ-COLL` provides the following exported readtables which may be used\nwhole with `NAMED-READTABLES:IN-READTABLE`, or with the readtable merging\nbehaviors of \n`NAMED-READTABLES:MAKE-READTABLE`,\n`NAMED-READTABLES:DEFREADTABLE` (via `:FUSE` or `:MERGE` options), and\n`NAMED-READTABLES:MERGE-READTABLES-INTO`.\n\n* `CLJ-COLL:READTABLE`           (for `IN-READTABLE`) provides both vector, set, and map syntax.\n* `CLJ-COLL:READTABLE-MAP-MIXIN` provides mergeable syntax for just maps.\n* `CLJ-COLL:READTABLE-SET-MIXIN` provides mergeable syntax for just sets.\n* `CLJ-COLL:READTABLE-VECTOR-MIXIN` provides mergeable syntax for just vectors.\n\n## Readtable munging functions\n\nIn addition to the named readtables above, there are functions\nyou can use to clobber and unclobber existing read-tables as follows:\n\n* `CLJ-COLL:ENABLE-MAP-SYNTAX`     - enables map read-table syntax\n* `CLJ-COLL:ENABLE-SET-SYNTAX`     - enables set read-table syntax\n* `CLJ-COLL:ENABLE-VECTOR-SYNTAX`  - enables vector read-table syntax\n* `CLJ-COLL:DISABLE-MAP-SYNTAX`    - disables map read-table syntax\n* `CLJ-COLL:DISABLE-SET-SYNTAX`    - disables set read-table syntax\n* `CLJ-COLL:DISABLE-VECTOR-SYNTAX` - disables vector read-table syntax\n\n# Print representations of vectors, sets, and maps\n\nIf you're using the reader syntax, it's common for clojure programmers to\nlike to print vectors/sets/arrays with the same syntax used for reading. \n\nIt may not actually be readable if you use it in Common Lisp depending on\nwhen and how you use the Clojure syntax in conjunction with CLJ-COLL, but\nfor Clojure programmers it is generally nicer to see the content of\ncollections rather than something like `#\u003cSOME-VECTOR-TYPE 0xDEADBEEF\u003e`.\n\nCLJ-COLL by default does not enable special printing syntax, however you\ncan enable it by using the following functions:\n\n* `CLJ-COLL:ENABLE-MAP-PRINTING`     - enable pretty printing for maps\n* `CLJ-COLL:ENABLE-SET-PRINTING`     - enable pretty printing for sets\n* `CLJ-COLL:ENABLE-VECTOR-PRINTING`  - enable pretty printing for vectors*\n* `CLJ-COLL:ENABLE-PRINTING`         - enable pretty printing for maps/sets/vectors\n* `CLJ-COLL:DISABLE-MAP-PRINTING`    - disable pretty printing for maps\n* `CLJ-COLL:DISABLE-SET-PRINTING`    - disable pretty printing for sets\n* `CLJ-COLL:DISABLE-VECTOR-PRINTING` - disable pretty printing for vectors(*)\n* `CLJ-COLL:DISABLE-PRINTING`        - disable pretty printing for maps/sets/vectors\n\n(*): `CLJ-COLL::ENABLE-VECTOR-PRINTING` only enables special printing for\nimmutable vectors.  Common Lisp vectors (and importantly, strings,\nwhich are vectors), are left as is.  It's also useful to see `#(1 2)`\nvs `[1 2]`, because you know the first is mutable, and the second is not.\n\nNote that the augmented printing only works if `*PRINT-PRETTY*` is true,\nsuch as by calling `PPRINT` (which binds the variable to true). The default\nvalue is implementation dependent. (SBCL's is T, CCL's is NIL, for example).\n\n## Printing of mutable vs. immutable maps, and immutable lists\n\nThe print methods attempt to print contents of collections such that you\ncan see the elements for debugging subject to `*print-length*`, and so that\nyou can, perhaps, read the printed representations back in, though little\ntime has been spent worrying about that.\n\n### Printing lists\n\nGiven that Common Lisp and CLJ-COLL use the standard lisp list syntax to\nread CL lists, we choose not to print the _persistent_ lists with the same\nsyntax.  Persistent lists are printed with `(list 1 2 ...)` intead of `(1 2 ...)`\nso that you know when you're dealing with a persistent list. \n\nIf you're in the CLJ-COLL package and/or using the shadows symbols then\n`list` will create a persistent list. The printer deliberately eschews\nprinting the package qualified symbol (e.g. `(CLJ-COLL:LIST ...)`) because\nit's more ugliness than I can stand, which means that persistent lists will\nprint as `(list 1 2 ..)` even if you print it from the `CL-USER` package.\n\n### Printing maps\n\nSimilar to the issue of overloading print representations for lists, we\nhave the same issue for CL:HASH-TABLE vs persistent hash maps.\n\nThe CLJ-COLL map printer provided by `enable-map-printing` \nwill print immutable maps using the reader syntax, i.e. `{:a 1 :b 2}`.\nIt will mutable cl:hash-table maps as `(cl-hash-table :a 1 :b 2)`,\n`cl-hash-table` being a CLJ-COLL constructor for such maps.\n\n## PRINT-OBJECT methods are not used by on CL collections, here's why\n\nFirst, a note on what conforming Common Lisp programs may and may not do\nwith regard to customized printing and `PRINT-OBJECT`:\n\n1. [22.4 PRINT-OBJECT](https://www.lispworks.com/documentation/HyperSpec/Body/f_pr_obj.htm)\n   \"Users may write methods for print-object for their own classes\" with no\n   mention of allowability for implementation classes/types.\n\n2. [11.2.1.2](https://www.lispworks.com/documentation/HyperSpec/Body/11_abab.htm)\n   \"Except where explicitly allowed, the consequences are undefined if any\n   of the following actions are performed on an external symbol of the\n   COMMON-LISP package: ... 19. Defining a method for a standardized\n   generic function which is applicable when all of the arguments are\n   direct instances of standardized classes.\" \n\nMore simply, defining PRINT-OBJECT methods on Common Lisp standard types\nmay lead to problems. Indeed, this was encountered trying to define the method\nfor CL:VECTOR types while developing CLJ-COLL.\n\nFortunately the venerable if flawed Common Lisp standard has not left us in\ntne lurch, it privides for the `PPRINT-DISPATCH` mechanism which allows us to\nnot only define _pretty printing_ behavior for common lisp types, but also\nallows you to override these if you like, without clobbering system\n`PRINT-OBJECT` methods.\n\n# Some boring notes on CLJ-COLL's immutable datatypes\n\nLike Clojure, CLJ-COLL provides (directly or indirectly) immutable Cons\ncells, PersistentList objects (which do _not_ use persistent Conses, and\nwhich are O(1) countable unlike CL lists), and persistent sets, vectors,\nand maps.\n\nDo not expect a Clojure/CLJ-COLL Cons to behave like a Common Lisp cons.\nThey are not used the same way in Clojure, and CLJ-COLL goes to some\ntrouble to behave like Clojure when operating on immutable conses and\nlists.  In Clojure, the immutable Cons is more like a \"glue\" datatype,\nallowing you to string together multiple collections and [lazy]seqs into a\nlarger collection. \n\nAnd of course a persistent cons is the generally returned value from lazy\nseq thunk.  Little known tidbit, you can return data types other than Cons\ncells from Clojure (and CLJ-COLL) lazy seqs!\n\nIn terms of integration with CL, each of CL:CONS, CLJ-COLL:CONS,\nCLJ-COLL:PERSISTENTLIST all act as \"seqs\", supporting `first`, `next`, and\n`rest` behaviors.\n\n# Emacs tips for brace-delimited form travel\n\nOnce you start using Clojure map syntax in your Common Lisp code, e.g.\n\n    (let ((m {:a 1 :b 2 :c {:d 4}}))\n       ...)\n\nYou may find that your stock `lisp-mode` form travel with `forward-sexp`\nand related functions does not seem to treat braces as form delimiters.\n\nA fix that seems to work is to put the following in `lisp-mode-hook` in\n`.emacs`:\n\n    (add-hook\n      'lisp-mode-hook\n      (lambda () \n        ; ... whatever you already have, if anything\n        (modify-syntax-entry ?\\{ \"(}\" lisp-data-mode-syntax-table)  ;for form traveling\n        (modify-syntax-entry ?\\} \"){\" lisp-data-mode-syntax-table)))\n\n*TBD*, the following _may_ be useful as well:\n\n    (modify-syntax-entry ?\\{ \"(}\" lisp-mode-syntax-table)       ;for highlighting\n    (modify-syntax-entry ?\\} \"){\" lisp-mode-syntax-tabletable)\n\n\nI'm not much for customizing emacs, please let me know if there are\nproblems with this and provide the proper solution to use. I also don't use `paredit`\nor `smart-parens` or all those other tools and cannot avise on that.  Let\nme know if you have tips.\n\n# API differences from Clojure\n\nCLJ-COLL functions that match CLojure functions are 100% compatible with\nClojure semantics, however the semantics are sometimes extended, i.e. a superset,\nof Clojure functionality. However there are some semantic differences:\n\n* clojure.set namespace APIs reside in the CLJ-COLL package, so instead of\n  calling `(clojure.set/union ...)` it's just `(union ...)`.  Note that\n  UNION shadows the Common Lisp function of the same name, as do many\n  Clojure functions.\n\n* There are no Clojure/java data types, so instead of returning, for\n  example, a `clojure.lang.PersistentList`, a function may instead return a\n  CLJ-COLL PersistentList (or other) type.  Normally you don't have to\n  worry about this when using Clojure collection/seq APIs, it all just works.\n\n* Set/Map membership tests have tighter equality semantics (as documented\n  elsewhere in this file) than Clojure.\n\n## APIs\n\nBearing in mind that we strive for more-or-less `EQUAL` comparison behavior\nin all `CLJ-COLL` data APIs, the following functions create data structures\nusable by the APIs.\n\n* `hash-map`    creates a new immutable hash table\n* `cl-hash-map` creates a new mutable hash table with `equal` equality\n* `vector`      creates a new immutable vector\n* `cl-vector`   creates a new mutable vector (adjustable and fill pointered)\n* `hash-set`    creates a new immutable hash set\n* `list`        creates a new persistent list\n* `queue`       creates a new immutable FIFO queue, vs `clojure.lang.PersistentQueue/EMPTY`\n\n# Tested Lisps\n\nTesting done on Fedora 40 invoking `clj-coll-test:run-tests` in each lisp.\nI've listed results in approximate decending order of success.\n\nTl;DR: SBCL, CCL, and ECL were champs. ABCL was okay,\nand in an ironic twist, free versions of the _commercial_ lisps failed even\nto load dependencies without running out of and/or corrupting memory.\n\nIf you have wisdom to share on Lispworks or Allegro Common Lisp let me\nknow, its also possible I failed to install or launch them, correctly. \n\n\n- SBCL 2.4.6 =\u003e EXCELLENT\n\n- CCL 1.13   =\u003e VERY GOOD\n\n  * SET-PRINTING was overly aggressive with `*PRINT-RIGHT-MARGIN*`.\n    Cause not investigated. May have applied to map printing too.\n\n  * Could not figure out how to have constant structs reliably used with EQ in CCL\n    and had to change `+EMPTY-LIST+` and friends from constant values to\n    `*EMPTY-LIST*` `defvar`s.\n\n- ECL 24.5.10 =\u003e VERY GOOD\n\n  * Just once, and I couldn't reproduce it, I got the environment in a\n    state where these two tests started failing after working the previously.\n \n         SET-PRINTING in TEST-SUITE []: \n              Unexpected Error: #\u003ca SIMPLE-ERROR 0x7f3b19866480\u003e\n         Tried to modify a read-only pprint dispatch table: #\u003cpprint-dispatch-table  0x7f3b241a07c0\u003e.\n\n         VECTOR-PRINTING in TEST-SUITE []: \n              Unexpected Error: #\u003ca SIMPLE-ERROR 0x7f3b198666c0\u003e\n         Tried to modify a read-only pprint dispatch table: #\u003cpprint-dispatch-table  0x7f3b241a07c0\u003e.\n\n- ABCL 1.9.2, OpenJDK 21.0.5 =\u003e OKAY\n\n  * Structure comparison via MOP interfaces didn't work. As\n    this is not a default operational mode of `CLJ-COLL:EQUAL?` I've disabled\n    the structure comparison test for now with ABCL.\n\n    Error was:\n    ```\"The value #(SYSTEM::DEFSTRUCT-SLOT-DESCRIPTION CLJ-COLL-TEST::A 0\n    CLJ-COLL-TEST::TEST-STRUCT-A NIL T NIL) is not of type #\u003cSTANDARD-CLASS\n    SYSTEM:SLOT-DEFINITION {4DA27116}\u003e.\" on a call to `MOP:SLOT-DEFINITION-NAME`.```\n\n    CLOS instance equivalence tests were also disabled until such time as\n    the test code for standard-class and structure-class equality is split\n    into discrete pieces.\n\n  * ABCL also had a pretty messy bunch of warnings loading all the\n    dependencies, but apparently nothing fatal for CLJ-COLL.\n\n- Allegro CL Express 11.0 (`alisp` executable) =\u003e UNABLE TO TEST, MULTIPLE FAILURES\n\n    I checked for a more recent Allegro versions, 11.0 has been out for a while, but\n    there was nothing. I also keep looking for switches to increae memory\n    to `alisp` but it doesn't respond to any --help or similar arguments\n    and the online Franz docs are not helpful.  Somehow it's hard to\n    believe it couldn't obtain the requested 64MB of memory noted below.\n    \n    Attempts to test CLJ-COLL failed while trying to load dependencies.\n    First, loading `shinmera-random-state` failed with ```\"Error: Attempt to\n    take the value of the unbound variable `EXCL::.CASE-FAILURE'.\"```\n\n    I skipped past that error seemingly without issue, only to have the lisp die\n    while loading FSET-USER. With the error ```\".Allegro CL(pid 667495):\n    System Error (gsgc) Couldn't get 63438848 bytes from operating system\n    The internal data structures in the running Lisp image have been\n    corrupted and execution cannot continue.\"```\n\n- LispWorks 8.0.1 Personal Edition. =\u003e UNABLE TO TEST, MEMORY FAILURE\n\n    Couldn't even load the modest dependencies without running out of memory\n    on the personal edition.\n\n# CLJ-COLL non-goals\n\n* Implementing the Clojure language or special form syntax (e.g. CLojure's destructuring) \n  is not a goal.  CLJ-COLL still relies on DEFUN, CL:DEFMACRO, CL:LET, CL:LOOP, and so on.\n  It may be that CLJ-COLL gives you enough syntax and such to implement\n  another library for all those other Clojure control forms and `[]`\n  syntax, if you want to try.\n\n* There is no attempt to make CLJ-COLL trivially extensible for arbitrary\n  new collection types.  Defining the matrix of interoperation between many\n  collection types, seqs on them, and equivalence, is a fairly nitty-gritty\n  detail-oriented thing.` There's a reason there are so many assertions in\n  the unit tests.\n\n# Related projects, differences from CLJ-COLL\n\nIt seems to be a rite of passage for programmers who like both Clojure and\nCommon Lisp to implement varying amounts of Clojure in Common Lisp. The\nreverse tends not to be true (implementing Common Lisp constructs in\nClojure), but kudos to the people who implemented \n[symbol-macrolet](https://github.com/clojure/tools.macro) in CLojure.  :-)\n\nTwo notable projects Clojure-in-CL projects are:\n\n* [Cloture](https://github.com/ruricolist/cloture) An implementation of\n  Clojure in Common Lisp.\n* [Clclojure](https://github.com/joinr/clclojure) An experimental port of\n  Clojure to Common Lisp.\n  \nBoth of these are shooting for bigger goals than `CLJ-COLL`.\n\n# Frequently asked questions\n\n1. Q: How do I turn various collection types into CL:LIST types?\n   A: Use `mconcat`, or consider an appropriate M function for the call which\n      generated the non-cl:list collection in the first place.\n\n2. Q: How do I use transducers to produce mutable collection results?\n   A: Use the appropriate mutable 'init' value to `transduce` or `into`, and\n      `cl-conj` as your reducing function.\n\nAstute readers will wonder why the initial release of CLJ-COLL claims to\nhave frequently asked questions.  It's because these are questions \u0026\nanswers for which the author had to remind himself repeatedly during the project.\n\n# Additional CLJ-COLL reading/notes\n\n* [Deferred or rejected APIs](./deferred-or-rejected-apis.md)\n* [Future Work](./future-work.md)\n* [Implementation/performance notes](./performance-notes.md)\n* [Mutable data, destructive functions, API conventions](./MIMO-Quandaries.md)\n* [Miscellaneous performance notes / speed tests](./MiscPerformanceNotes.md)\n\n# Feedback welcome\n\ngitrepo-feedback@protonmail.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtenny%2Fclj-coll","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdtenny%2Fclj-coll","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtenny%2Fclj-coll/lists"}