سؤال

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?

هل كانت مفيدة؟

المحلول 4

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

نصائح أخرى

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.

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)
    ))

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top