Skip to content

Commit b6830a4

Browse files
committed
let-values
Also valid-type-specifier-p is more bombproof This will be 9.0.0
1 parent b43d459 commit b6830a4

File tree

6 files changed

+342
-11
lines changed

6 files changed

+342
-11
lines changed

README.md

+40-2
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,41 @@ Logging to pathnames rather than explicitly-managed streams may be a little slow
24462446
### Package, module
24472447
`slog` lives in and provides `:org.tfeb.hax.slog`.
24482448

2449+
## Binding multiple values: `let-values`
2450+
`let-values` provides four forms which let you bind multiple values in a way similar to `let`: you can bind several sets of them in one form. The main benefit of this is saving indentation.
2451+
2452+
**`let-values`** is like `let` but for multiple values. Each clause consists of a list of variables and an initform, rather than a single variable. The variables are bound to the multiple values returned by the form. Variables are bound in parallel as for `let`. The initform may be omitted which is equivalent to it being `nil`. The equivalent of the 'bare variable' case for `let` is not supported (why would you use `let-values` for this!).
2453+
2454+
**`let*-values`** is like `let*` but for multiple values. Each clause is bound within the scope of the previous clauses.
2455+
2456+
**`let-values*`** is a variant of `let-values` which has the semantics of `multiple-value-call`: Each clause may have a number of initforms, and the variables are bound to the combined values of all of the forms.
2457+
2458+
**`let*-values*`** is to `let-values*` as `let*-values` is to `let-values`.
2459+
2460+
Declarations should be handled properly (`(declare (type fixnum ...))` is better than `(declare (fixnum ...))` but both should work). As an example
2461+
2462+
```lisp
2463+
(let-values (((a b) (floor 2.4)) ((c d) (floor 8.5)))
2464+
(declare (integer a c) (single-float b d))
2465+
(values (+ a b) (+ c d)))
2466+
-> (multiple-value-bind (#:a #:b)
2467+
(floor 2.4)
2468+
(declare (type integer #:a))
2469+
(declare (type single-float #:b))
2470+
(multiple-value-bind (#:c #:d)
2471+
(floor 8.5)
2472+
(declare (type integer #:c))
2473+
(declare (type single-float #:d))
2474+
(let ((a #:a) (b #:b) (c #:c) (d #:d))
2475+
(declare (integer a c) (single-float b d))
2476+
(values (+ a b) (+ c d)))))
2477+
```
2478+
2479+
In the `let*`-style cases the declarations will apply to all duplicate variables.
2480+
2481+
### Package, module
2482+
`let-values` lives in and provides `:org.tfeb.hax.let-values`.
2483+
24492484
## Utilities
24502485
This is used both by other hax and by other code I've written. Things in this system *may not be stable*: it should be considered mostly-internal. However, changes to it *are* reflected in the version number of things, since other systems can depend on things in it.
24512486

@@ -2456,7 +2491,8 @@ Here is what it currently provides.
24562491
- `with-names` binds variables to uninterned symbols with the same name by default: `(with-names (<foo>) ...)`will bind `<foo>` to a fresh uninterned symbol with name `"<FOO>"`. `(with-names ((<foo> foo)) ...)` will bind `<foo>` to a fresh uninterned symbol with name `"FOO"`.
24572492
- `thunk` makes anonymous functions with no arguments: `(thunk ...)` is `(lambda () ...)`.
24582493
- `thunk*` makes anonymous functions which take an arbitrary number of arguments and ignore them all.
2459-
- `valid-type-specifier-p` attempts to answer the question 'is something a valid type specifier?'. It does this by asking `subtypep` if it's a subtype of `t`, on the assumption that *any* valid type specifier should be a recognizable subtype of `t`. There is an optional second argument which is an environment object: using this lets it answer the question for the compilation environment: see [this CLHS issue](https://www.lispworks.com/documentation/HyperSpec/Issues/iss334.htm "Issue `SUBTYPEP-ENVIRONMENT:ADD-ARG` Summary").
2494+
- `valid-type-specifier-p` attempts to answer the question 'is something a valid type specifier?'. It does this, normally[^22], by asking checking that `(typep nil <thing>)` does not signal an error, which I think is a loophile-free way of answering this question (SBCL, again, says , incorrectly, that`(subtypep <thing> t)`is tue for any `<thing>` thich looks even slightly like a type specifier). There is an optional second argument which is an environment object handed to `typep`: using this lets it answer the question for the compilation environment: see [this CLHS issue](https://www.lispworks.com/documentation/HyperSpec/Issues/iss334.htm "Issue `SUBTYPEP-ENVIRONMENT:ADD-ARG` Summary").
2495+
- `canonicalize-declaration-specifier` attempts to turn the shorthand `(<type> ...)` declaration specifier into a canonical `(type <type> ...)`. It does this using `valid-type-specifier-p`. Its optional second argument is an environent object passed to `valid-type-specifier-p`. The spec says that a declaration identifier is 'one of the symbols [...](#); *or a symbol which is the name of a type*' [my emphasis]. This means that a declaration specifier like `((integer 0) ...)` is not legal. Several implementations accept these however, so this function blindly turns such things into type declaration specifiers. It returns a second value which will be false for one of these, true otherwise.
24602496

24612497
### Package, module
24622498
The utilities live in and provide `:org.tfeb.hax.utilities`.
@@ -2507,4 +2543,6 @@ The TFEB.ORG Lisp hax are copyright 1989-2024 Tim Bradshaw. See `LICENSE` for t
25072543

25082544
[^20]: An interim version of `slog` had a generic function, `log-entry-formatter` which was involved in this process with the aim of being able to select formats more flexibly, but it did not in fact add any useful flexibility.
25092545

2510-
[^21]: Well: you could write your own `handler-bind` / `handler-case` forms, but don't do that.
2546+
[^21]: Well: you could write your own `handler-bind` / `handler-case` forms, but don't do that.
2547+
2548+
[^22]: SBCL has its own version of this function, so that's used for SBCL.

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.8.0
1+
9.0.0

let-values.lisp

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
;;;; LET-VALUES & its friends
2+
;;;
3+
4+
#+org.tfeb.tools.require-module
5+
(org.tfeb.tools.require-module:needs
6+
(:org.tfeb.hax.spam :compile t)
7+
(:org.tfeb.hax.collecting :compile t)
8+
(:org.tfeb.hax.iterate :compile t)
9+
(:org.tfeb.hax.utilities :compile t))
10+
11+
(defpackage :org.tfeb.hax.let-values
12+
(:use :cl)
13+
(:use
14+
:org.tfeb.hax.spam
15+
:org.tfeb.hax.collecting
16+
:org.tfeb.hax.iterate
17+
:org.tfeb.hax.utilities)
18+
(:export
19+
#:let-values
20+
#:let*-values
21+
#:let-values*
22+
#:let*-values*))
23+
24+
(in-package :org.tfeb.hax.let-values)
25+
26+
(provide :org.tfeb.hax.let-values)
27+
28+
(defun make-vme (name starred)
29+
(cons name (if starred name (make-symbol (symbol-name name)))))
30+
31+
(defun vme-name (vme)
32+
(car vme))
33+
34+
(defun vme-hidden (vme)
35+
(cdr vme))
36+
37+
(defun mapped-variable-declarations (declarations varmap environment)
38+
;; Appropriate variable declarations from DECLARATIONS mapped
39+
;; through VARMAP 'Appropriate' means type declarations and
40+
;; dynamic-extent declarations. SPECIAL never matters (the hidden
41+
;; variable can be lexical).
42+
(flet ((mapped-variables (varmap variables)
43+
(collecting
44+
(dolist (vme varmap)
45+
(when (member (vme-name vme) variables)
46+
(collect (vme-hidden vme)))))))
47+
(collecting
48+
(dolist (declaration declarations)
49+
(dolist (specifier (mapcar (lambda (d)
50+
(canonicalize-declaration-specifier d environment))
51+
(rest declaration)))
52+
(destructuring-bind (identifier . rest) specifier
53+
(case identifier
54+
(type
55+
(destructuring-bind (type . vars) rest
56+
(let ((mapped-variables (mapped-variables varmap vars)))
57+
(unless (null mapped-variables)
58+
(collect `(declare (type ,type ,@mapped-variables)))))))
59+
(dynamic-extent
60+
(let ((mapped-variables (mapped-variables varmap rest)))
61+
(unless (null mapped-variables)
62+
(collect `(declare (,identifier ,@mapped-variables)))))))))))))
63+
64+
(defun expand-lv (clauses decls/forms starred environment &aux (unique-variables '()))
65+
(unless (matchp clauses (list-of (some-of (list-matches (list-of (var)) (any))
66+
(list-matches (list-of (var))))))
67+
(error "bad clauses ~S" clauses))
68+
(if (null clauses)
69+
`(locally ,@decls/forms)
70+
(multiple-value-bind (varmaps vfs)
71+
(with-collectors (varmap vf)
72+
(dolist (clause clauses)
73+
(varmap
74+
(with-collectors (vme)
75+
(destructuring-bind (vars &optional (form nil)) clause
76+
(vf form)
77+
(dolist (var vars)
78+
(unless starred
79+
(when (member var unique-variables)
80+
(error "~S is not unique" var))
81+
(push var unique-variables))
82+
(vme (make-vme var starred))))))))
83+
(assert (= (length varmaps) (length vfs)) () "botched")
84+
(let ((declarations (nth-value 0 (parse-simple-body decls/forms))))
85+
(iterate mvb ((vms varmaps) (forms vfs))
86+
(destructuring-bind (vm . more-vms) vms
87+
(destructuring-bind (form . more-forms) forms
88+
`(multiple-value-bind ,(mapcar #'vme-hidden vm) ,form
89+
,@(mapped-variable-declarations declarations vm environment)
90+
,(if (not (null more-vms))
91+
(mvb more-vms more-forms)
92+
`(let ,(mapcan (lambda (varmap)
93+
(mapcar (lambda (vme)
94+
`(,(vme-name vme) ,(vme-hidden vme)))
95+
varmap))
96+
varmaps)
97+
,@decls/forms))))))))))
98+
99+
(defmacro let-values ((&rest clauses) &body decls/forms &environment environment)
100+
"Multiple-value LET form with parallel binding
101+
102+
LET-VALUES is like LET but each clause binds a list of variables to
103+
the multiple values of its initform. Bindings for different clauses
104+
happen in parallel, as for LET, so
105+
106+
(let ((a 1))
107+
(let-values (((a) 2)
108+
((b) a))
109+
(values a b)))
110+
111+
evaluates to 2 and 1
112+
113+
An initform may be omitted, when it will be NIL. The equivalent of (let (a b
114+
...) ...) is not allowed.
115+
116+
Declarations should be handled correctly and perhaps usefully."
117+
(expand-lv clauses decls/forms nil environment))
118+
119+
(defmacro let*-values ((&rest clauses) &body decls/forms &environment environment)
120+
"Multiple-value LET form with sequential binding
121+
122+
LET-VALUES is like LET but each clause binds a list of variables to
123+
the multiple values of its initform. Bindings for different clauses
124+
happen in sequence, as for LET*, so
125+
126+
(let ((a 1))
127+
(let*-values (((a) 2)
128+
((b) a))
129+
(values a b)))
130+
131+
evaluates to 2 and 2
132+
133+
An initform may be omitted, when it will be NIL. The equivalent of (let (a b
134+
...) ...) is not allowed.
135+
136+
Declarations should be handled correctly and perhaps usefully."
137+
(expand-lv clauses decls/forms t environment))
138+
139+
(defun expand-lv* (clauses decls/forms starred environment &aux (unique-variables '()))
140+
(unless (matchp clauses (list-of (list*-matches (list-of (var)) (any))))
141+
(error "bad clauses ~S" clauses))
142+
(if (null clauses)
143+
`(locally ,@decls/forms)
144+
(multiple-value-bind (varmaps vis)
145+
(with-collectors (varmap vi)
146+
(dolist (clause clauses)
147+
(varmap
148+
(with-collectors (vme)
149+
(destructuring-bind (vars . forms) clause
150+
(vi forms)
151+
(dolist (var vars)
152+
(unless starred
153+
(when (member var unique-variables)
154+
(error "~S is not unique" var))
155+
(push var unique-variables))
156+
(vme (make-vme var starred))))))))
157+
(assert (= (length varmaps) (length vis)) () "botched")
158+
(let ((declarations (nth-value 0 (parse-simple-body decls/forms))))
159+
(iterate mvc ((vms varmaps) (initforms vis))
160+
(destructuring-bind (vm . more-vms) vms
161+
(destructuring-bind (this-initforms . more-initforms) initforms
162+
`(multiple-value-call
163+
(lambda ,(mapcar #'vme-hidden vm)
164+
,@(mapped-variable-declarations declarations vm environment)
165+
,(if (not (null more-vms))
166+
(mvc more-vms more-initforms)
167+
`(let ,(mapcan (lambda (varmap)
168+
(mapcar (lambda (vme)
169+
`(,(vme-name vme) ,(vme-hidden vme)))
170+
varmap))
171+
varmaps)
172+
,@decls/forms)))
173+
,@this-initforms))))))))
174+
175+
(defmacro let-values* ((&rest clauses) &body decls/forms &environment environment)
176+
"Multiple-value LET-like form with parallel binding
177+
178+
Each clause in LET-VALUES* consists of a list of variables to bind and
179+
any number of initforms, including zero. The variables are bound to
180+
the combined values of all the initforms: this is the same as
181+
MULTIPLE-VALUE-CALL, which this uses. Example:
182+
183+
(let-values* (((a b c) (values 1 2) 3))
184+
(values a b c))
185+
186+
evaluates to 1, 2 and 3
187+
188+
Bindings for different clauses happen in parallel, as for LET, so
189+
190+
(let ((a 1))
191+
(let-values* (((a) 2)
192+
((b) a))
193+
(values a b)))
194+
195+
evaluates to 2 and 1.
196+
197+
There may be no initforms which binds all variables to NIL. The
198+
equivalent of (let (a b ...) ...) is not supported.
199+
200+
Declarations should be handled correctly, and perhaps usefully"
201+
(expand-lv* clauses decls/forms nil environment))
202+
203+
(defmacro let*-values* ((&rest clauses) &body decls/forms &environment environment)
204+
"Multiple-value LET-like form with sequential binding
205+
206+
Each clause in LET-VALUES* consists of a list of variables to bind and
207+
any number of initforms, including zero. The variables are bound to
208+
the combined values of all the initforms: this is the same as
209+
MULTIPLE-VALUE-CALL, which this uses. Example:
210+
211+
(let-values* (((a b c) (values 1 2) 3))
212+
(values a b c))
213+
214+
evaluates to 1, 2 and 3
215+
216+
Bindings for different clauses happen in sequence, as for LET*, so
217+
218+
(let ((a 1))
219+
(let-values* (((a) 2)
220+
((b) a))
221+
(values a b)))
222+
223+
evaluates to 2 and 2.
224+
225+
There may be no initforms which binds all variables to NIL. The
226+
equivalent of (let (a b ...) ...) is not supported.
227+
228+
Declarations should be handled correctly, and perhaps usefully."
229+
(expand-lv* clauses decls/forms t environment))

org.tfeb.hax.let-values.asd

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
;;;; Module org.tfeb.hax.let-values of org.tfeb.hax
2+
;;;
3+
4+
(in-package :asdf-user)
5+
6+
(defsystem "org.tfeb.hax.let-values"
7+
:description
8+
"A subsystem of the TFEB hax"
9+
:version
10+
(:read-file-line "VERSION")
11+
:author
12+
"Tim Bradshaw"
13+
:license
14+
"MIT"
15+
:homepage
16+
"https://github.com/tfeb/tfeb-lisp-hax"
17+
:depends-on
18+
("org.tfeb.hax.spam"
19+
"org.tfeb.hax.collecting"
20+
"org.tfeb.hax.iterate"
21+
"org.tfeb.hsx.utilities")
22+
:components
23+
((:file "let-values")))

spam.lisp

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
#+org.tfeb.tools.require-module
55
(org.tfeb.tools.require-module:needs
6-
(:org.tfeb.hax.simple-loops :compile t))
6+
(:org.tfeb.hax.simple-loops :compile t)
7+
(:org.tfeb.hax.utilities :compile t))
78

89
(defpackage :org.tfeb.hax.spam
9-
(:use :cl :org.tfeb.hax.simple-loops)
10+
(:use :cl :org.tfeb.hax.simple-loops :org.tfeb.hax.utilities)
1011
(:export
1112
#:head-matches
1213
#:list-matches
@@ -191,11 +192,14 @@
191192
(funcall p e))
192193
effective-predicates))))
193194

195+
;;; Matching
196+
;;;
197+
194198
(defun matchp (thing predicate)
195199
(funcall predicate thing))
196200

197201
(defmacro matching (form &body clauses)
198-
(let ((<var> (make-symbol "V")))
202+
(with-names (<var>)
199203
`(let ((,<var> ,form))
200204
(cond
201205
,@(mapcar (lambda (clause)

0 commit comments

Comments
 (0)