From f3a56a7020811f5a4d02364b8b85f7f1a3a0f13b Mon Sep 17 00:00:00 2001 From: Tim Bradshaw Date: Sat, 26 Aug 2023 08:52:05 +0100 Subject: [PATCH] simple-loops: dolists is dolist for many lists --- README.md | 22 +++++++++++++++++++++ VERSION | 2 +- simple-loops.lisp | 50 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f1d699b..1407ea4 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,16 @@ and now (1 2) ``` +It would be possible, perhaps, to gain some efficiency by declaring the collectors `dynamic-extent`, but I've chosen not to do that so the semantics is more coherent. If you want the efficiency you can always make the appropriate declaration yourself: + +```lisp +(with-collectors (c) + (declare (dynamic-extent (function c))) + ...) +``` + +will work, for instance. + `collecting` is older than `with-collectors` by more than a decade I think. However it has an obvious definition as a shim on top of `with-collectors` and, finally, that now *is* its definition. See `collect-into` below, which can be handed a local collector function as an argument and will do the right thing. This means that, for instance this will work: @@ -1667,6 +1677,18 @@ will evaluate to `1 2 3 1` for instance (and will not loop at all). **`escaping`** provides a general named escape construct. `(escaping ( &rest ) ...)` binds `` as a local function which will immediately return from `escaping`, returning either its arguments as multiple values, or the values of `` as multiple values. The forms in `` are not evaluated if they are not used: if they are evaluated they're done in their lexical environment but in the dynamic environment where the escape function is called. +**`dolists`** is like `dolist` but for multiple lists. Additionally it has more control over what value or values are returned. The syntax is `(dolists (( []) ...) ...)`, and as many values are returned as there are ``s. Thus + +```lisp +(dolists ((v1 '(1 2 3)) + (v2 '(1 2 3 4) v2)) + (format t "~&~S ~S~%" v1 v2)) +``` + +will print each value of `v1` and `v2` with the last being `3` and `3`, and then return `4`. There is a subtelty here: for `dolist` the variable is `nil` at the point the iiteration terminates: `(dolist (v '(1 2 3) v))` evaluates to `nil`. `dolists` generalises this: at the point the iteration terminates at least one of the variables will be `nil`. The variables which are *not* `nil`, corresponding to lists longer than the shortest list, will be bound the the vakue of the next element of their list. + +As with `dolist` it is not specified whether the iteration variable is rebound for each iteration, or a single binding is mutated. + `escaping` is obviously a shim around `(block ... (return-from ...) ...)` and there are the same constraints on scope that blocks have: you can't call the escape function once control has left the form which established it. The `passing` family of functions *aren't* named `while` because they're not actually `while` loops as the bind variables and also I didn't want to take such a useful and short name: everyone knows what `(while (= x 3) ...)` means. diff --git a/VERSION b/VERSION index 8104cab..fbb9ea1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.1.0 +8.2.0 diff --git a/simple-loops.lisp b/simple-loops.lisp index be216a2..9fd2fda 100644 --- a/simple-loops.lisp +++ b/simple-loops.lisp @@ -26,6 +26,7 @@ :org.tfeb.hax.utilities) (:export #:doing #:doing* + #:dolists #:passing #:passing* #:do-passing #:do-passing* #:failing #:failing* @@ -262,12 +263,56 @@ of the last form in the body." (looping/values (( ...)
) ...) binds the s to the values of and hhen updates them with the values of the last form in the body. (looping/values (( ...) ...) binds the -s to the cobined values of all the 's and hen updates them +s to the cobined values of all the 's and then updates them with the values of the last form in the body. As with LOOPING there is no termination condition but the body is wrapped in a block named NIL so RETURN will work." `(looping/values* ((,variables ,@form/s)) ,@decls/body)) +(defmacro dolists (bindings &body decls/forms) + "Like DOLIST but with multiple lists + +Each BINDING is either (var init) or (var init value). As many values +are returned as bindings with value forms, so this may return between +no values and as manu values as bindings. The inits are evaluated in +parallel (nothing else really makes much sense). The loop ends when +the first list terminates. At least one the of variables will +therefore be NIL when the loop terminates (same as for DOLIST, whee +the only variable is NIL when the loop terminates)." + ;; This was briefly done with DOING, but this seems nicer. Note + ;; this implementetion rebinds the variables on each iteration. + (multiple-value-bind (vars tail-vars inits values) + (with-collectors (var tail-var init value) + (dolist (binding bindings) + (typecase binding + (cons + (let ((l (list-length binding))) + (unless (and (<= 2 l 3) + (symbolp (first binding))) + (error "binding ~S is malformed" binding)) + (var (first binding)) + (init (second binding)) + (tail-var (make-symbol (concatenate 'string (string (first binding)) + "-TAIL"))) + (when (= l 3) + (value (third binding))))) + (t + (error "hopeless binding ~S" binding))))) + (multiple-value-bind (decls forms) (parse-simple-body decls/forms) + `(looping ,(mapcar #'list tail-vars inits) + (let ,(mapcar (lambda (var tail-var) + `(,var (car ,tail-var))) + vars tail-vars) + ,@decls + (when (or ,@(mapcar (lambda (tail) + `(null ,tail)) + tail-vars)) + (return (values ,@values))) + ,@forms + (values ,@(mapcar (lambda (tail-var) + `(cdr ,tail-var)) + tail-vars))))))) + (defmacro escaping ((escape &rest defaults) &body forms) "Bind a local escape function @@ -289,5 +334,6 @@ ESCAPING." (if (null args) (values ,@defaults) (values-list args))))) - (declare (inline ,escape)) + (declare (inline ,escape) + (dynamic-extent (function ,escape))) ,@forms))))