Skip to content

Commit

Permalink
simple-loops: dolists is dolist for many lists
Browse files Browse the repository at this point in the history
  • Loading branch information
tfeb committed Aug 26, 2023
1 parent be368e2 commit f3a56a7
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 3 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 (<escaper> &rest <defaults>) ...)` binds `<escaper>` as a local function which will immediately return from `escaping`, returning either its arguments as multiple values, or the values of `<defaults>` as multiple values. The forms in `<defaults>` 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 ((<var> <list-form> [<result-form>]) ...) ...)`, and as many values are returned as there are `<result-form>`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.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8.1.0
8.2.0
50 changes: 48 additions & 2 deletions simple-loops.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
:org.tfeb.hax.utilities)
(:export
#:doing #:doing*
#:dolists
#:passing #:passing*
#:do-passing #:do-passing*
#:failing #:failing*
Expand Down Expand Up @@ -262,12 +263,56 @@ of the last form in the body."
(looping/values ((<var> ...) <form>) ...) binds the <var>s to the
values of <form> and hhen updates them with the values of the last
form in the body. (looping/values ((<var> ...) <form> ...) binds the
<var>s to the cobined values of all the <form>'s and hen updates them
<var>s to the cobined values of all the <form>'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
Expand All @@ -289,5 +334,6 @@ ESCAPING."
(if (null args)
(values ,@defaults)
(values-list args)))))
(declare (inline ,escape))
(declare (inline ,escape)
(dynamic-extent (function ,escape)))
,@forms))))

0 comments on commit f3a56a7

Please sign in to comment.