Reputation: 12202
I'm fond of Lisp, but one of the thing I find irksome about it is that it nests too much.
In an imperative programming language, I can break a long expression by using an intermediate value, for instance:
int x = someFunctionCall() ? someOtherFunctionCall() : 42;
int y = myUnterminableNameFunction(x);
instead of
int x = myUnterminableNameFunction(someFunctionCall() ? someOtherFunctionCall() : 42);
This can be done in Lisp too, but as far as I'm aware, only by using let
. let
introduces an additional level of nesting, which I'd rather avoid.
I'm not looking to argue that opinion, but to find a way to declare local variable in a single, non-nesting function/macro call. Something like declare_local
in the following:
(defun my_function (a b)
(declare_local x (if (some_function_call) (some_other_function_call) 42))
(my_unterminable_name_function x))
If it does not exist, can it maybe be implemented via a clever macro, without it being detrimental to performances?
Upvotes: 3
Views: 2598
Reputation: 58578
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.
(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.
Upvotes: 5
Reputation: 35298
Here's a proof of concept macro that pulls variable declarations from a flat list up into standard let*
forms.
(defun my/vardecl-p (x)
"Return true if X is a (VAR NAME VALUE) form."
(and (listp x)
(> (length x) 1)
(eq 'var (car x))))
(defmacro my/defun (name args &rest body)
"Special form of DEFUN with a flatter format for LET vars"
(let ((vardecls (mapcar #'cdr
(remove-if-not #'my/vardecl-p body)))
(realbody (remove-if #'my/vardecl-p body)))
`(defun ,name ,args
(let* ,vardecls
,@realbody))))
Example:
(my/defun foo (a b)
(var x 2)
(var y 3)
(* x y a b))
(foo 4 5)
; => 120
Upvotes: 4
Reputation: 1671
You may use let*
form for sequential binding.
(let* ((x (if (some-function-call)
(some-other-call)
42))
(y (my-unterminable-name-function x)))
(bla-bla-bla)
...)
It does nest, but not so much.
As of declare-local
, it has to be processed by some outer macro. For example, you may write my-defun
that checks for declare-local
in its body and transforms it. Upd but it is somewhat anti-Lisp. Lisp forms usually affect only nested forms. cl:declare
is the only exception I can think of.
Upvotes: 8
Reputation: 71065
Yes, in Common Lisp use &aux
arguments:
(defun foo (a b &aux x y z)
(setq x ...)
(setq y ...)
.... )
Or use the "prog
feature":
(defun bar (a b)
(prog (x y)
(setq x a y b)
....
(return 42)
))
Upvotes: 6