No, there are no forms which can occur in a progn
-like body and suddenly declare new variables which have a scope over the remainder of the progn
.
While this would be convenient in some ways (such as less indentation and fewer whitespace-only diffs in version control), there is a big down-side: it is a lot harder to write code which analyzes code.
Lisp is structured so that when we look at the leftmost symbol of a compound form, we know what it is, and there is some rigid, easy to parse syntax next to that symbol which tells us what symbols, if any, are introduced over the scope of that construct. (If it does such a thing, it is called a binding construct).
A binding construct that can have variable definitions scattered throughout the body requires extra work to find all these places.
If you really miss this feature from other languages, you can write yourself a macro which will implement it for you.
Here is a possible start.
Let us call the macro (begin ...)
, and inside (begin ...)
let us support syntax of the form (new (var [initform])*)
which introduces one or more variables using let
syntax, except that it has no body. The scope of these variables is the remainder of the begin
form.
The task, then, is to make the macro transform syntax of this form:
(begin
a b c
(new (x 42))
d e
(new (y 'foo) (z))
f g)
into, say, this code:
(progn
a b c
(let ((x 42))
d e
(let ((y 'foo) (z))
f g)))
The begin
macro has to look at all of its argument forms, and tell apart the (new ...)
ones from anything else, and generate the nested let
structure.
The above transformation problem has a structure which suggests a straightforward recursive solution.
A modern industrial-strength begin
will have to provide for declarations. Perhaps we can allow a (new ...)
form to be immediately followed by a (declare ...)
form. The two are then folded according to the pattern ((new A ...) (declare B ...) C ...)
-> (let (A ...) (declare B ...) C ...)
, where we recursively process (C ...)
for more occurrences of (new ...)
.
Of course, if you have this begin
macro, you have to explicitly use it. There isn't any easy way to retarget existing Lisp constructs which have an "implicit progn" such that they have an "implicit begin".
Of course, what you can always do is implement a very complicated macro which looks like this:
(my-dialect-of-lisp
;; file full of code in your customized dialect of Lisp goes here
)
The my-dialect-of-lisp
macro parses the dialect (i.e. implements a full code walker for that dialect) and spits out a translation into standard Lisp.
Appendix: Implementation of (begin ...)
(Without declaration support):
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun begin-expander (forms)
(if (null forms)
nil
(destructuring-bind (first &rest rest) forms
(if (and (consp first)
(eq (first first) 'new))
`((let (,@(rest first)) ,@(begin-expander rest)))
`(,first ,@(begin-expander rest)))))))
(defmacro begin (&rest forms)
(let ((expansion (begin-expander forms)))
(cond
;; (begin) -> nil
((null expansion) nil)
;; (begin (new ...) ...) -> ((let (...) ...)) -> (let (...) ...)
((and (consp (first expansion))
(eq (first (first expansion)) 'let))
(first expansion))
;; (begin ...) -> (...) -> (progn ...)
(t `(progn ,@expansion)))))
This kind of a thing is better expressed with the help of a pattern matching library.