1

I am building a window application in Lisp using the LTK library. I want a button that does an action and, possibly, hides itself. However, this code:

(let* ((left (button 0 0 f "←" #'(lambda ()
                                   (decf start page-length)
                                   (funcall redraw)
                                   (if (>= start page-length)
                                       (ltk:configure left :state :visible))
                                       (ltk:configure left :state :hidden))))))

claims that "left" is an undefined variable (the rest is defined in code beyond the scope of this problem).

Worst case scenario, I avoid the "button" function I wrote and rework the code for this particular situation, but the scenario begs a general solution. Is there any way in Lisp to use a variable in a function in the definition of the variable?

2
  • 1
    Is letrec what you're looking for? Commented Sep 21, 2020 at 20:28
  • @psmears Seems to be, but I need its equivalent in Common Lisp. Commented Sep 21, 2020 at 20:38

2 Answers 2

4

A let* with only one binding is the same as a let binding. A let binding does not exist until the body is executed. During the execution of button the reference for left must be from an earlier closure or global as left is created after the expression is evaluated. You can do this:

(let ((left nil))
  (setf left (button 0 0 f "←" #'(lambda ()
                                   (decf start page-length)
                                   (funcall redraw)
                                   (if (>= start page-length)
                                       (ltk:configure left :state :visible)
                                       (ltk:configure left :state :hidden))))))

NB: There was a bug in the if such that the lambda always would execute (ltk:configure left :state :hidden)

Sign up to request clarification or add additional context in comments.

Comments

2

For what it's worth here is a version of letrec in CL:

(defmacro letrec (bindings &body decls/forms)
  (assert (and (listp bindings)
               (every (lambda (b)
                        (or (symbolp b)
                            (and (consp b)
                                 (symbolp (first b))
                                 (null (cddr b)))))
                      bindings))
      (bindings) "malformed bindings")
  (multiple-value-bind (names values)
      (loop for b in bindings
            collect (etypecase b
                      (symbol b)
                      (cons (first b)))
            into vars
            collect (etypecase b
                      (symbol nil)
                      (cons (second b)))
            into vals
            finally (return (values vars vals)))
      `(let ,names
         (psetf ,@(loop for name in names
                       for val in values
                       collect name
                       collect val))
         (locally
           ,@decls/forms))))

Then

> (letrec ((x (lambda (y)
                (if (null y)
                    'done
                  (funcall x (cdr y))))))
    (funcall x '(1 2 3)))
done

4 Comments

perhaps s/psetf/setf, to better fit the Scheme semantics? (also, you've let one car slip through, instead of the first :) ). also, a Scheme coder would normally expect to be able to tail-call the letrec-defined function, and CL has no TCO guarantee... I'm not sure how/whether this could be mitigated (or whether it even should be mitigated).
I've changed the setf to psetf: thanks. For car / first: I use first/ rest when I'm thinking about a list and car cdr when I'm thinking about pairs. But I've not been consistent here for sure: car for the name of a binding and second for the value: it should either be first & second or car and cadr. Not sure which! For TCO I just treat CL as if it did and don't use implementations which don't!
first, second, and rest - about lists; car/cdr - about cons cells/pairs; makes sense. seeing both car and first refer to the same thing is ... weird. :)
@WillNess: I've changed it as you're clearly right. The thing which was derailing me (after having forgotten how it worked) is that the sanity check at the start really does need to use cddr because it's trying to check for a 2-elt list but doesn't want to use length (may fail to terminate, may raise exception) or list-length (may raise exception). CL needs a safe pattern-matching library (I'm sure there is one in Quicklisp, but I did not want some huge dependency for the macro) like Racket has.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.