{"id":42264234,"url":"https://github.com/slburson/misc-extensions","last_synced_at":"2026-04-02T18:41:07.684Z","repository":{"id":11573343,"uuid":"14061702","full_name":"slburson/misc-extensions","owner":"slburson","description":"The GMap iteration macro, plus a few other useful macros.","archived":false,"fork":false,"pushed_at":"2026-03-27T00:44:39.000Z","size":137,"stargazers_count":22,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-27T02:36:48.306Z","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":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/slburson.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":"2013-11-02T05:43:27.000Z","updated_at":"2026-03-27T00:44:43.000Z","dependencies_parsed_at":"2024-12-30T23:22:16.259Z","dependency_job_id":"49610bf2-9c05-4d2f-8f15-23e582580a3f","html_url":"https://github.com/slburson/misc-extensions","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/slburson/misc-extensions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slburson%2Fmisc-extensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slburson%2Fmisc-extensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slburson%2Fmisc-extensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slburson%2Fmisc-extensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/slburson","download_url":"https://codeload.github.com/slburson/misc-extensions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slburson%2Fmisc-extensions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31313171,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":"2026-01-27T06:12:49.602Z","updated_at":"2026-04-02T18:41:07.677Z","avatar_url":"https://github.com/slburson.png","language":"Common Lisp","readme":"# Misc-Extensions\n\nThe Misc-Extensions system provides several macros that I like to use.\n\nSee `src/defs.lisp` for the package declarations.\n\n## 1. Macros to bind multiple values\n\nFor writing code in a mostly-functional style, Common Lisp's multiple-value\nfeature is extremely useful (I make heavy use of it in\n[FSet](https://github.com/slburson/fset), for example, both internally and in\nthe API).  But the most important primitive the language provides for receiving\nmultiple values from a call, `multiple-value-bind`, is a bit clunky to use.\nFirst, its long name would be appropriate for a rarely used operation, but I, at\nleast, want to do this a lot.  Second, combining it with other bindings can be\ndone only by nesting; where `cl:let` and `cl:let*` let you bind multiple\nvariables to the values of their respective init-forms, `multiple-value-bind`\nreceives values from only a single form.\n\nMisc-Extensions provides three related macros to elegantly and succinctly bind\nlists of one or more variables to the one or more values of their respective\ninit-forms.  All of these allow more than one variable in a binding clause; the\nlast element of the clause is the init-form, and the preceding elements are the\nvariables to be bound to the values of the init-form.\n\nThe two that I expect most people will want to use are `mvlet` and `mvlet*`.\n`mvlet`, like `cl:let`, makes all bindings in parallel, so the init-forms can\nrefer to variables of the same names in the containing scope.  `mvlet*`, like\n`cl:let*`, makes them sequentially, so each init-form can refer to any of the\nvariables bound in the preceding clauses.  Except for being able to bind\nmultiple values, they are almost perfectly upward-compatible with the CL\noperators, with a minor caveat I will return to below.\n\n`nlet` generalizes the other two by making it possible to do any combination of\nparallel and sequential binding.  It allows the binding clauses to be nested to\nindicate sequential binding: more deeply nested clauses are within the scopes of\nbindings made by less deeply nested clauses.  Within a level, bindings are\nparallel.  I'm personally fond of `nlet`, but my guess is that most people will\nfind the syntax a little off-putting.\n\nHere are examples of all three.\n\n```common-lisp\n  (let ((a (foo)))\n    ...\n    (mvlet ((a b (bar))\n            (c (baz a))  ; outer 'a'\n            ...)\n      ...)\n\n    (mvlet* ((a b (bar))\n             (c (baz a))  ; inner 'a'\n             ...)\n      ...)\n\n    (nlet ((a b (bar))\n           (c (baz a))  ; outer 'a'\n           ...)\n      ...)\n\n    (nlet ((a b (bar))\n           ((c (baz a)))  ; inner 'a'\n           ...)\n      ...))\n```\n\nIn all four examples we see `a` and `b` being bound to the first two values of\n`(bar)` in the first clause, but whether the reference to `a` in the second\nclause refers to the `a` in the outer scope or the one that was just bound\ndepends on whether `mvlet` or `mvlet*` was used, or in the `nlet` case, the\nnesting level of the second clause.\n\nA more complex `nlet` example:\n\n```common-lisp\n  (nlet ((a b c (zot))\n         ((d (quux a c))\n          ((e f (mumble b d))\n           (g (mung a))))\n         ((h (frobozz c))\n          ((i (xyzzy h))))\n         (*print-level* 3))\n    ...)\n```\n\nFirst `a`, `b`, and `c` are bound to the first three values of `(zot)`, and in\nparallel, `*print-level*` is bound to 3; then `d` and `h` are bound; then `e`,\n`f`, `g`, and `i` are bound.\n\nAs this example illustrates, all bindings at a given nesting level are done in\nparallel, with all bindings at a deeper level following.  Stylistically, it is\nexpected that init-forms in nested clauses will refer only to variables bound in\ncontaining clauses.  However, this is not enforced; for instance, the init-form\nfor `g` could refer to `h`, since the latter is bound one level out.\n\nThe macros correctly handle `declare` forms at the beginning of the body,\nemitting the declarations at the correct level within the expansion so that they\napply to the binding named.  (If there are multiple bindings of the same name,\nany declarations on that name apply only to the innermost one.)\n\nThe symbol `nlet` is exported from package `new-let`.  It also exports the same\nmacro under the name `let`, so that it can shadow `cl:let`; this is how I use\nit, though again, I suspect most people will not want to do that.  Anyway,\nbecause `new-let:let` is exported, if you `(:use new-let)` in your package\ndeclaration, you will also need to include a `:shadowing-import-from` clause to\nsay which version of `let` you want to use.\n\n`mvlet` and `mvlet*` are initially exported from `new-let` as well, but to make\nthem a little more convenient to use (especially for those not yet expert in\ndealing with CL packages), I created another package `mvlet` that re-exports\nonly these two symbols.  So you can say `(:use mvlet)` to get them, without\nhaving to shadowing-import anything.\n\nHistorical note: I first wrote `nlet` in 1980, on the MIT Lisp Machine, and have\nused it ever since in my own projects.\n\n## 2. GMap\n\nGMap is an iteration macro for Common Lisp.  It was conceived as a\ngeneralization of `mapcar` (hence the name).  It is intended for cases when\n`mapcar` doesn't suffice because the things you want to map over aren't in\nlists, or you need to collect the results of the mapping into something other\nthan a list.\n\nThat is, `gmap` is probably the right thing to use when you are using iteration\nto perform the same computation on each element of some collection, as opposed\nto changing your state in some complicated way on every iteration of a loop.\nIt's conceptually reasonable to imagine all the iterations of a `gmap` as\nhappening in parallel, just as you might with `mapcar`.  It also supports\narbitrary reductions of the results of the mapped function; more on this below.\n\nGMap is explicitly intended only for iterations that fit this \"map-reduce\"\nmodel.  It is not trying to be a \"universal\" iteration construct.  People have\nasked me what guarantees it offers concerning the ordering of the various\noperations it performs, and the answer is none, other than those obviously\nimposed by the data flow (a result can't be used until it is computed).  I think\nthat the question was rooted in experience of people using `loop` or other\niteration constructs and supplying variable update expressions with side\neffects, so that there was \"crosswise\" data flow between the iterations.  I\nstrongly advise that such side effects be avoided in `gmap` calls.  If you find\nyourself wanting to use them, most likely `gmap` simply isn't the right tool for\nthe job (maybe `reduce` would work better).  In short, you should think of\n`gmap` very much as a _functional_ iteration construct.\n\nIn general, my philosophy about iteration in Lisp is that there are many ways to\ndo it, for the very good reason that there are many kinds of iterations, and one\nshould use the tool appropriate for the particular case one is confronted with.\nSo, for example, I almost never write a `gmap` form with side effects.  If I'm\njust iterating over a list for effect, I'll use `dolist`.  For iterations with\ncross-iteration data flow (\"loop-carried dependences\" is the compiler\ndeveloper's term) or where the iteration space is not well defined in advance\n(e.g., iterating over lines being read from a file) I might use good old `do`,\nor I might even write the code tail-recursively, counting on the fact that most\nCL implementations these days do tail-call optimization at least between\nfunctions defined in a single `labels` form.\n\nSo when I do use `gmap`, it's specifically intended to convey to someone reading\nthe code that the function being mapped is side-effect-free, so that the calls\nto it are independent of one another.  I strongly urge adherence to this rule.\n\nEven with that constraint, I find that occasions to use `gmap` are not at all\nuncommon.  It has proven handy over the years.\n\nThe difference between `mapcar` and `gmap` is that with `gmap`, you explicitly\nindicate what kinds of collections the elements come from and how to combine the\nsuccessive values of the mapped function into a result.  For example, the\nfollowing two expressions are equivalent:\n\n```common-lisp\n(mapcar #'foo this-list that-list)\n```\nand\n```common-lisp\n(gmap (:result list) #'foo (:arg list this-list) (:arg list that-list))\n```\n\n[Side note to existing GMap users: this is the new, GMap 4.0 syntax.  The older\nsyntax is considered mildly deprecated, but will continue to be supported\nindefinitely.  More on this below.]\n\nThe `(:result list)` subform indicates that `gmap` is to build a list; the\n`:arg` subforms tell it that `this-list` and `that-list` are in fact lists of\nelements over which `foo` is to be mapped.  Other types are known besides\n`list`; for example, `string`, if used as an argument type, causes its argument\nto be viewed as a string; the values it supplies to the function being mapped\nare the successive characters of the string.\n\nLike `mapcar`, `gmap` accepts any number of argument specs; each one supplies\none argument (or more, in some cases) to each call to the function being mapped.\nAlso like `mapcar`, `gmap` terminates its iteration when any of the arguments\nruns out of elements.\n\nFor a small collection of examples, look at `test-new-syntax` in `tests.lisp`.\n\nHistorical note: GMap is now one of several extensible iteration macros, but\nit's actually among the oldest; along with `nlet`, I first wrote it in 1980, in\nLisp Machine Lisp.\n\n### 2.1. Mapped function\n\nThe mapped function is called with one or two arguments from each argument\ngenerator (the ones that generate two arguments are noted below).  It may return\nmultiple values; some result collectors make use of the additional values\n(again, see below).\n\nA literal `:id` may be supplied as an abbreviation for the identity function;\nit is equivalent to `#'values`.  (`nil` may be written instead of `:id`, but\nthis usage is a bit unclear and is deprecated.)\n\n### 2.2. Argument types\n\nThe set of argument types is extensible.  Thus you can adapt `gmap` to other\nkinds of data structures over which you would like to iterate.  For details of\nhow to do this, see `def-arg-type` in the source file, and study the existing\ndefinitions.\n\nAn argument type can explicitly indicate that it supplies more than one argument\nto the function being mapped.  That function must have one or more additional\nparameters at the corresponding point in its parameter list.  For example:\n\n```common-lisp\n(gmap (:result list) #'(lambda (x y z) (cons x (+ y z)))\n      (:arg alist '((a . 47) (b . 72)))\n      (:arg list '(2 6)))\n==\u003e\n((A . 49) (B . 78))\n```\n\nHere `x` and `y` receive the pairs of the alist, and `z` receives the elements\nof the list.\n\n#### 2.1.1. Predefined argument types\n\n- `constant` _value_: Yields `value` on every iteration.\n\n- `list` _list_: Yields successive elements of `list`.\n\n- `improper-list` _list_: Yields the successive elements of `list`, which may be\n  improper; any non-cons tail terminates the iteration.\n\n- `alist` _alist_: Yields, as two values, the successive pairs of `alist`.\n\n- `plist` _plist_: Yields, as two values, the successive pairs of elements of\n  `plist`; that is, there is one iteration for each two elements.\n\n- `hash-table` _table_: Yields, as two values, the successive pairs of `table`.\n  (Warning: the ordering of pairs is Lisp-implementation-dependent and should not\n  be relied on.)\n\n- `tails` _list_: Yields the successive tails (cdrs) of `list`, starting with\n  `list` itself, which may be improper.\n\n- `index` \u0026optional _start_ _stop_ \u0026key _incr fixnums?_: Yields integers in the\n  interval [`start`, `stop`) if `incr` (which defaults to 1) is positive; or in\n  the interval [`stop`, `start`) if `incr` is negative.  Specifically, in the\n  upward case, the values begin with `start` and increase by `incr` until \u003e=\n  `stop`; in the downward case, the values begin with `start` - `incr` and\n  decrease by `incr` until \u003c `stop`.  All values are assumed to be fixnums unless\n  `fixnums?` is a literal `nil`.  `stop` can be omitted or a literal `nil` to\n  indicate an unbounded sequence.  `start` can be omitted to start at 0.\n\n- `index-inc` _start stop_ \u0026key _incr fixnums?_: (\"Index, INClusive\") Yields\n  integers in the interval [`start`, `stop`].  Specifically, in the upward case\n  (`incr` \u003e 0), the values begin with `start` and increase by `incr` until \u003e\n  `stop`; in the downward case, the values begin with `start` and decrease by\n  `incr` until \u003c `stop`.  All values are assumed to be fixnums unless `fixnums?`\n  is a literal `nil`.  `stop` can be a literal `nil` to indicate an unbounded\n  sequence.\n\n- `exp` _initial-value base_: Yields an exponential sequence whose first value\n   is `initial-value`, and whose value is multiplied by `base` on each\n   iteration.\n\n- `vector` _vec_ \u0026key _start stop incr_: Yields elements of vector `vec`.  `start`\n  and `stop` may be supplied to select a subsequence of `vec`; `incr` may be\n  supplied (it must be positive) to select every second element etc.  For\n  performance, you may prefer `simple-vector`.\n\n- `simple-vector` _vec_ \u0026key _start stop incr_: Yields elements of vector `vec`,\n  which is assumed to be simple, and whose size is assumed to be a fixnum.\n  `start` and `stop` may be supplied to select a subsequence of `vec`; `incr`\n  may be supplied (it must be positive) to select every second element etc.\n\n- `string` _str_ \u0026key _start stop incr_: Yields elements of string `str`.  `start`\n  and `stop` may be supplied to select a subsequence of `vec`; `incr` may be\n  supplied (it must be positive) to select every second element etc.  For\n  performance, you may prefer `simple-string`.\n\n- `simple-string` _str_ \u0026key _start stop incr_: Yields elements of string `str`,\n  which is assumed to be simple, and whose size is assumed to be a fixnum.\n  `start` and `stop` may be supplied to select a subsequence of `str`; `incr`\n  may be supplied (it must be positive) to select every second element etc.\n\n- `array` _ary_ \u0026key _start stop incr_: Yields elements of array `ary`, which\n  may be multidimensional and/or specialized.  Access is via `row-major-aref`,\n  so the elements are yielded in row-major order.  `start` and `stop`, which are\n  row-major indices, may be supplied to select a subsequence, and `incr` to skip\n  one or more elements at each step.\n\n- `file-chars` _pathname_ \u0026key _element-type external-format_: Yields the\n  characters of the file named by `pathname`.  `element-type` and\n  `external-format`, if supplied, are passed to `open`.\n\n- `file-lines` _pathname_ \u0026key _skip-initial external-format_: Yields the lines\n  of the file named by `pathname`.  If `skip-initial` is given, it is the number\n  of initial lines to skip.  `external-format`, if supplied, is passed to `open`.\n\n### 2.3. Result types\n\nGMap, unlike `mapcar`, has the ability to perform arbitrary reductions on the\nresults returned by the function being mapped.  So, cases where you might have\nwritten\n\n```common-lisp\n(reduce #'+ (mapcar ...))\n```\n\ncan be replaced with a single `gmap` call, which is also more efficient in that\nit doesn't materialize the intermediate result list:\n\n```common-lisp\n(gmap (:result sum) ...)\n```\n\nGMap takes the view that consing up a collection of function results is a kind\nof reduction — a slightly unusual view, perhaps, but not unreasonable.  So it\ntreats collecting the results and summing them, for example, as instances of the\nsame pattern.\n\nAs with the argument types, the set of result types is extensible.  For details\nof how to do this, see `def-result-type` in the source file, and study the\nexisting definitions.\n\nA result type can explicitly indicate that it expects the function being mapped\nto return multiple values, which it can turn into multiple arguments to a\nreduction function.  Also, there is the special result type `values`, which\ntakes two or more result specs, and expects the function being mapped to return\nthe same number of values; it reduces each value according to the corresponding\nresult spec, then finally returns all the reduction results as multiple values.\nFor example:\n\n```common-lisp\n(gmap (:result values list sum) #'(lambda (x y) (values x y))\n      (:arg alist '((a . 7) (b . 19))))\n==\u003e\n(A B)   ; first value\n26      ; second value\n```\n\nAdditionally, there is a `:consume` feature that allows a single reduction to\nconsume multiple values from the function being mapped; see the source for\ndetails.\n\n\n#### 2.2.1. Predefined result types\n\n- `list` \u0026key _filterp_: Returns a list of the values, optionally filtered by\n  `filterp` (which can be `:id` to filter out `nil`).\n\n- `alist` \u0026key _filterp_: Consumes two values from the mapped function; returns\n  an alist of the pairs.  Note that `filterp`, if supplied, must take two\n  arguments.\n\n- `plist` \u0026key _filterp_: Consumes two values from the mapped function; returns\n  a plist of the pairs.  Note that `filterp`, if supplied, must take two\n  arguments.\n\n- `hash-table` \u0026key _test_ _size_ _rehash-size_ _rehash-threshold_ _filterp_:\n  Consumes two values from the mapped function; returns a hash-table of the\n  pairs.  If any of `test`, `size`, `rehash-size`, or `rehash-threshold` are\n  supplied, they are passed to `make-hash-table`.  Note that `filterp`, if\n  supplied, must take two arguments.\n\n- `append` \u0026key _filterp_: Returns the result of `append`ing the values,\n  optionally filtered by `filterp` (which can be `:id` to filter out `nil`).\n\n- `nconc` \u0026key _filterp_: Returns the result of `nconc`ing the values,\n  optionally filtered by `filterp` (which can be `:id` to filter out `nil`).\n\n- `and`: If one of the values is false, terminates the iteration and returns\n  false; otherwise returns the last value.  Does not work as an operand of\n  `values`.  (Generalizes `cl:every`.)\n  \n- `or`: If one of the values is true, terminates the iteration and returns it;\n  otherwise, returns false.  Does not work as an operand of `values`.\n  (Generalizes `cl:some`.)\n\n- `sum` \u0026key _filterp_: Returns the sum of the values, optionally filtered by\n  `filterp` (which can be `:id` to filter out `nil`).\n\n- `product` \u0026key _filterp_: Returns the product of the values, optionally\n  filtered by `filterp` (which can be `:id` to filter out `nil`).\n\n- `count`: Returns the number of true values.\n\n- `max` \u0026key _filterp_ _key_: Optionally filters the values by `filterp`, then\n   returns the maximum, or if `key` is supplied, the value with the maximum key\n   (if that's not unique, returns the first one); or `nil` if no values were\n   supplied (or survived filtering).  Example:\n   ```\n   (gmap (:result max :key #'cdr) nil (:arg list alist))\n   ```\n   returns the (first) pair of `alist` with the maximum `cdr`.\n\n   If `key` is `:second-value`, the second value of the mapped function is used;\n   for example,\n   ```\n   (gmap (:result max :key :second-value) nil (:arg alist an-alist))\n   ```\n   returns the (first) `car` of `an-alist` with the maximum corresponding `cdr`.\n\n- `min` \u0026key _filterp_ _key_: Optionally filters the values by `filterp`, then\n   returns the minimum, or if `key` is supplied, the value with the minimum key\n   (if that's not unique, returns the first one); or `nil` if no values were\n   supplied (or survived filtering).  Example:\n   ```\n   (gmap (:result min :key #'cdr) nil (:arg list alist))\n   ```\n   returns the (first) pair of `alist` with the minimum `cdr`.\n\n   If `key` is `:second-value`, the second value of the mapped function is used;\n   for example,\n   ```\n   (gmap (:result min :key :second-value) nil (:arg alist an-alist))\n   ```\n   returns the (first) `car` of `an-alist` with the minimum corresponding `cdr`.\n\n- `vector` \u0026key _use-vector length fill-pointer adjustable filterp_: Constructs\n  a vector containing the results.  If `use-vector` is supplied, the argument\n  will be filled with the results and returned; if `fill-pointer` is true and\n  `adjustable` is true, it must have a fill pointer and be adjustable, and\n  values will be appended to it with `vector-push-extend`; if `fill-pointer` is\n  true and `adjustable` is false, it must have a fill pointer, and values will\n  be appended to it with `vector-push`; otherwise, the vector is assumed to be\n  simple and must be large enough to hold the results.  (Recall that\n  `vector-push` has no effect if the vector is full.)\n\n  If `use-vector` is not supplied, a vector will be constructed and returned;\n  if `length` is supplied, returns a simple vector of the specified length (which\n  must be sufficient to hold the results); otherwise, returns a simple vector of\n  the correct length (but to do this, it must cons a temporary list).\n\n  In any case, if `filterp` is supplied, it is a predicate of one argument,\n  the value of the function being mapped, that says whether to include it in\n  the result (`filterp` can be `:id` to filter out `nil`).\n\n- `string` \u0026key _use-string length fill-pointer adjustable filterp_: Constructs\n  a string containing the results.  If `use-string` is supplied, the argument\n  will be filled with the results and returned; if `fill-pointer` is true and\n  `adjustable` is true, it must have a fill pointer and be adjustable, and\n  values will be appended to it with `vector-push-extend`; if `fill-pointer` is\n  true and `adjustable` is false, it must have a fill pointer, and values will\n  be appended to it with `vector-push`; otherwise, the vector is assumed to be\n  simple and must be large enough to hold the results.  (Recall that\n  `vector-push` has no effect if the vector is full.)\n\n  If `use-string` is not supplied, a string will be constructed and returned; if\n  `length` is supplied, returns a simple string of the specified length (which\n  must be sufficient to hold the results); otherwise, returns a simple string of\n  the correct length (but to do this, it must cons a temporary list).\n\n  In any case, if `filterp` is supplied, it is a predicate of one argument, the\n  value of the function being mapped, that says whether to include it in the\n  result (`filterp` can be `:id` to filter out `nil`).\n\n- `array` _dims_ \u0026key _element-type_ _initial-element_ _filterp_: Constructs an\n  array containing the results.  Passes `dims`, and `element-type` and\n  `initial-element` if supplied, to `make-array`.  If the array is\n  multidimensional, fills it in row-major order.\n\n### 2.4. The Old Syntax\n\nFor most of GMap's existence, it has had a slightly different syntax from that\nshown above.  The `:arg` and `:result` keywords were not used; instead, the\nargument and result types were defined as keywords themselves.  For instance,\nthe first example above would look like this:\n\n```common-lisp\n(gmap (:list) #'foo (:list this-list) (:list that-list))\n;; The parens around the result are optional; you can also write:\n(gmap :list #'foo (:list this-list) (:list that-list))\n```\n\nThe reason I made them keywords was so that uses of them, as in this example,\nwouldn't look like function calls; at least, the presence of the colons would\npresumably give the reader of the code, who might not be familiar with GMap, a\nclue that something out of the ordinary was going on.  The problem with this, of\ncourse, is that it abandoned the modularity which is the entire point of the\npackage system: there can be only one definition of a given keyword as an\nargument type or as a result type.\n\nThe 4.0 release fixes this by introducing `:arg` and `:result` to visually mark\nthese syntactic elements, and by changing all the predefined types to use\nnon-keyword symbols exported either from `cl:` or from `gmap:`.  However, it's\nimportant not to break existing code; so here's what I have done.\n`def-gmap-arg-type` and `def-gmap-res-type`, if given a name that is not a\nkeyword symbol, now also define the keyword symbol with the same name; but\nbefore they do that, they check that it is not already defined by a previous\ncall supplying a different non-keyword name; if it is, they signal an error.\n\nWith this change, the old syntax will continue to work, but collisions, where\ntwo systems try to define argument or result types with the same symbol-name,\nwill be detected; previously, the one loaded last would \"win\".\n\n(Personally, I've settled on a compromise, wherein I write result types `and`\nand `or` in the old syntax, as `:and` and `:or`; it's not plausible that these\ncould be defined as types.  For other result types, and all argument types, I\nuse the new syntax.)\n\n### 2.5. Examples\n\nTo return the position of the largest value in a list `numbers`:\n\n```\n(gmap (:result max :key :second-value) nil (:arg index 0) (:arg list numbers))\n```\n\n## 3. Macro `fn`\n\nFor very small lambda expressions, the six characters taken by the word `lambda`\ncan be a significant fraction of the total.  If the body doesn't reference all\nthe parameters, moreover, you'll want to add a `(declare (ignore ...))` for the\nunused ones, which adds to the verbosity.  The `fn` macro helps with both\nannoyances.  Obviously, its name is quite short.  (Those willing to set foot on\nthe slippery slope of Unicode could use `λ`, of course, but I've been afraid to\ngo there yet, lest my code wind up as an unintelligible mass of obscure\nmathematical symbols and cat emojis.)  And, you can use either a bare `_` or a\nname beginning with `_` as a parameter name, to indicate an ignored parameter;\n`fn` automatically inserts the required `ignore` declaration.\n\nOne catch, though, is that if you inadvertently write `#'(fn ...)`, you will get\na probably-unintelligible compiler error.  Just delete the `#'`.  (Lisp Machine\nLisp had something called \"lambda macros\" that solved this problem, but the\nfeature didn't make it into Common Lisp.)\n\n## 4. Lexical contexts\n\nI still consider these experimental and probably not terribly useful, though I do\nuse one occasionally.  If curiosity gets the better of you, have a look at\n`contexts.text`.  Briefly, it's an alternate syntax for combinations of `let`\nand `labels`.\n\n## 5. Global lexical variables\n\nMacro `deflex` _var_ \u0026optional _val doc_:\n\nDeclares `var` as a global lexical variable, and if `val` is supplied and\n`var` is not already bound, initializes it to `val`.  `doc`, if supplied,\nis taken as a documentation string.  In some implementations (e.g. Scieneer),\nlocally rebinding the same name is not permitted; in most, it is permitted\nbut creates a new lexical variable, with no effect on the global one.\n\nMacro `deflex-reinit` is the same except that it reinitializes the variable if\nit's already bound, like `cl:defparameter`.\n\n## 6. Interactive `setq`\n\nMacro `isetq` \u0026rest _var-val-pairs_:\n\nSome implementations, notably SBCL, issue a warning if you use `setq` to set a\nnew global variable in the REPL.  (I guess they want you to use `defvar` first.)\n`isetq` prevents this warning, using the same trick `deflex` uses to declare a\nglobal lexical.  `isetq` is ***not recommended for use in programs***.\n\n## 7. \"Reversed\" function binding forms\n\nIt's not uncommon to use `labels` to define a set of several mutually-recursive\nfunctions, whose code can fill a screen or two, then have the body of the\n`labels` kick off the recursion with a one-liner that just calls one of the\nfunctions.  This strikes me as a little hard to read — you have to scroll all\nthe way to the bottom to find the initial values of the recursion variables —\nand also a little wasteful of indentation space.  So I introduced a macro\n`rlabels`, which puts the initial call first as a single subform, then takes the\nfunction-binding clauses as its `\u0026body` parameter, saving 7 columns of\nindentation.\n\nThere are also `rflet` and `rmacrolet`, but I don't think I've ever used them.\n\n## 8. Succinct class definitions with `define-class`\n\nAs everyone knows, `defclass` forms tend to be rather verbose and repetitive:\n\n```common-lisp\n(defclass frob (widget)\n    ((color :initarg :color :reader frob-color\n       :initform (error \"A color must be specified.\")\n       :documentation \"The color of the frob.\")\n     (height :initarg :height :accessor frob-height\n       :initform 3.0\n       :documentation \"The height of the frob in cm.\"))\n  (:documentation \"The class of all frobs.\"))\n```\n\nI've written a `define-class` macro to shorten them.  It is completely upward\ncompatible with `defclass`; you could just replace `defclass` with\n`define-class` and have a valid invocation that would produce the same result.\nBut `define-class` provides several features to make the definition more\nsuccinct.  Use only the ones you like!  The big change is that where `defclass`\nslot descriptions have a strict alternating keyword-value syntax, `define-class`\nis more flexible.\n\n- A doc string for the class can be placed just before the slot specs (visually\n  similar to where doc strings for functions go).\n- The first element of a slot specifier, which is normally a slot name, can\n  instead be a list `(slot-name initform)`; an `:initform` option will be\n  generated.\n- `:may-init` generates `:initarg :slot-name`.\n- `:must-init` generates `:initarg :slot-name`, and also an `:initform` that\n  signals an error if the argument is not provided to `make-instance`.\n- `:readable` generates `:reader gf-name`, and `:accessible` generates\n  `:accessor gf-name`, where `gf-name` is either the slot name or, if a\n  `:conc-name` _class_ option is supplied, that string prepended to the slot\n  name.\n- Additionally, `:constant` is an abbreviation for `:must-init :readable`\n  if the spec contains no initform, or `:may-init :readable` if there is\n  an initform (in either syntax)\n- And `:variable` is an abbreviation for `:may-init :accessible`\n- A doc string can appear anywhere in the slot options; `:documentation` will be\n  inserted.\n- Or, you can use `:doc` as an abbreviation for `:documentation`.\n\nSo, here's the above example using `define-class`; 347 characters have\nbeen reduced to 186 — a 46% reduction — and I think it's actually\neasier to understand:\n\n```common-lisp\n(define-class frob (widget)\n  \"The class of all frobs.\"\n  ((color :constant \"The color of the frob.\")\n   ((height 3.0) :variable \"The height of the frob in cm.\"))\n  (:conc-name #:frob-))\n```\n\nLet me emphasize, you can still use the `defclass` slot options in any case\nwhere the above features do not do what you want; for instance, if you want a\ndifferent reader/accessor name for a particular slot than what `define-class`\nwould have used.\n\nBTW, for GNU Emacs users, if you look at the bottom of `src/define-class.lisp`,\nyou will see an Emacs patch that will improve the fontification of\n`define-class` slot doc strings, in the case where they are not preceded by\n`:documentation` / `:doc`.\n","funding_links":[],"categories":["Lisp parsers"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslburson%2Fmisc-extensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslburson%2Fmisc-extensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslburson%2Fmisc-extensions/lists"}