Binding Conditional Variables in Common Lisp

I want to execute a function with two local variables, but the values ​​of these variables must depend on some condition. For example, let's say I have 2 variables x

and y

, and I want to change them internally let

if y > x

. The exchange should be temporary, I don't want to change the state with rotatef

. My code will look something like this:

(setq x 2)
(setq y 1)
(let (if (> x y) ((x y) (y x)) ((x x) (y y)))
  (cons x y)) ; should return (1 . 2)

      

But the expression inside is let

not valid Lisp. How do I conditionally assign values ​​to local variables? The work around is to put the body in flet

and call it different arguments, but it looks awkward:

(flet ((body (x y) (cons x y)))
  (if (< x y)
      (body x y)
      (body y x)))

      

+3


source to share


6 answers


Multiple binding and values

There are many alternatives, some of which are already pointed out in other answers. I think the question in the title ("Binding Conditional Variables in Common Lisp") is a good example for multiple-value-bind and value . I've used different variable names in the following order to make it clear where x and y are and where the original values ​​come from. The names can be the same; it just shades them inside.

(let ((a 3)
      (b 2))
  (multiple-value-bind (x y)
      (if (< a b)
          (values a b)
          (values b a))
    (cons x y)))
;=> (2 . 3)

      

Then, using a bit of macrology, we can make it a little cleaner, since coredump did :

(defmacro if-let (test bindings &body body)
  "* Syntax:
let ({var | (var [then-form [else-form]])}*) declaration* form* => result*
* Description: 
Similar to LET, but each binding instead of an init-form can have a
then-form and and else-form.  Both are optional, and default to NIL.
The test is evaluated, then variables are bound to the results of the
then-forms or the else-forms, as by LET."
  (let ((bindings (mapcar #'(lambda (binding)
                              (destructuring-bind (variable &optional then else)
                                  (if (listp binding) binding (list binding))
                                (list variable then else)))
                          bindings)))
    `(multiple-value-bind ,(mapcar 'first bindings)
         (if ,test
             (values ,@(mapcar 'second bindings))
             (values ,@(mapcar 'third bindings)))
       ,@body)))

      

(pprint (macroexpand-1 '(if-let (< x y) ((x x y)
                                         (y y x))
                         (cons x y))))

; (MULTIPLE-VALUE-BIND (X Y)
;     (IF (< X Y)
;         (VALUES X Y)
;         (VALUES Y X))
;   (CONS X Y))

      

(let ((a 3) (b 2))
  (if-let (< a b)
      ((x a b)
       (y b a))
    (cons x y)))
;=> (2 . 3)

      



Comparison with progv

In terms of usage, this bears some similarities to sindikat's answer , but multiple-value-bind sets the bindings just like let : lexical by default, but a global or local ad will make the bindings dynamic. On the other hand, progv sets up dynamic bindings. This means that if the bindings are fully injected by progv , you won't see much difference (except for trying to return closures), but you cannot hide the bindings. We can see this without any conditional work at all. Here are two examples of snippets. In the first, we can see that the internal reference to x actually refers to the lexical anchor, not the dynamic one set by progv... In order to reference the value set by progv , you actually need to declare that the internal reference is special. progv does not accept declarations, but we can use locally .

(let ((x 1))
  (progv '(x) '(2)
    x))
;=> 1

      

(let ((x 1))
  (progv '(x) '(2)
    (locally (declare (special x))
      x)))
;=> 2

      

multiple-value-bind actually does the binding as we expected:

(let ((x 1))
  (multiple-value-bind (x) (values 2)
    x))
;=> 2

      

It is probably better to use a binding construct like multiple-value-bind , which sets the default lexical bindings, as let does .

+8


source


If you don't want to use progv

, as sindikat mentioned, you can always do something like this:

(defmacro let-if (if-condition then-bindings else-bindings &body body)
  `(if ,if-condition
     (let ,then-bindings
       ,@body)
     (let ,else-bindings
       ,@body)))

      

So an expression like



(let-if (> x y) ((x y) (y x)) ((x x) (y y))
       (cons x y))

      

Will expand to:

(IF (> X Y)
(LET ((X Y) (Y X))
  (CONS X Y))
(LET ((X X) (Y Y))
  (CONS X Y)))

      

+3


source


One solution is to use progv

instead let

, its first argument is a list of symbols to bind values, second argument is a list of values, rest is a body.

(progv '(x y) (if (< x y) (list x y) (list y x))
  (cons x y)) ; outputs (1 . 2)

      

+2


source


rotatef

What about:

CL-USER> (defvar x 2)
X
CL-USER> (defvar y 1)
Y
CL-USER> (let ((x x)    ; these variables shadow previously defined
               (y y))   ; X and Y in body of LET
           (when (> x y)
             (rotatef x y))
           (cons x y))
(1 . 2)
CL-USER> x              ; here the original variables are intact
2                       ; ^
CL-USER> y              ; ^
1                       ; ^

      

However, I think that in every such practical case, there are more meaningful ways to solve the problem without macros. Msandiford's answer is probably the best from a functional point of view.

psetf

While this rotatef

is a really efficient technique (it will probably compile down to about three machine instructions replacing pointers in memory), it is not generic.

Rainer Joswing posted just a great solution as a comment shortly after posting the question. To my shame I tested the macro psetf

just a few minutes ago and it should be a very efficient and general solution.

The macro psetf

first evaluates its even arguments, then assigns the evaluated values ​​to the variables at odd positions, as it does setf

.

So we can write:

(let ((x x)
      (y y))
  (when (> x y)
    (psetf x y y x))
  ...)

      

And that it is, you can conditionally remove anything. I think this is better than using macros. Because:

  • I do not consider this to be such a general situation;
  • Some of the macros in the posted answers repeat their body code, which can be really big: this way you end up with a larger compiled file (that's a fair price to use the macro, but not in this case);
  • Each custom macro makes the code easier to understand for other people.
+2


source


Another alternative might be:

(let ((x (min x y))
      (y (max x y)))
  (cons x y))

      

+1


source


My suggestion would be one of destructuring-bind

or multiple-value-bind

.

If you expect you need to do this a lot, I would suggest using a macro to create bindings. I have provided a possible macro (untested).

(defmacro cond-let (test-expr var-bindings &body body)
  "Execute BODY with the VAR-BINDINGS in place, with the bound values depending on 
   the trueness of TEST-EXPR.

   VAR-BINDINGS is a list of (<var> <true-value> <false-value>) with missing values 
   being replaced by NIL."

  (let ((var-list (mapcar #'car var-bindings))
        (then-values (mapcar #'(lambda (l)
                                 (when (cdr l) 
                                   (nth 1 l)))
                             var-bindings))
        (else-values (mapcar #'(lambda (l)
                                 (when (cddr l))
                                    (nth 2 l)))
                             var-bindings))
     `(destructuring-bind ,var-list
         (if ,test-expr
             (list ,@then-values)
           (list ,@else-values)))))

      

0


source







All Articles