1

In chapter 6 of "Let Over Lambda" I found anaphoric macro called alet. It works like let, but especially useful when the last form of alet body is a lambda expression, since it allows using of the lambda expression before it actually occurs in the text.

This is the first version of the macro, alet%:

(defmacro alet% (letargs &rest body)
  `(let ((this) ,@letargs)
     (setq this ,@(last body))
     ,@(butlast body)
     this))

So far, so good. But next the author decides to enhance the macro with the following reasoning:

alet% can be made to not return the last form in its body—which we anticipate to be a lambda form—but instead a function that looks up another function inside the let form's lexical scope, then calls that function instead. This is sometimes called indirection because instead of returning a function to do something, we return a function that looks up a function using a pointer dereference, then uses that function instead. Indirection is a concept ubiquitous throughout programming languages for good reason. It lets us change things at run-time that, without indirection, are fixed at compile-time.

alet is defined as:

(defmacro alet (letargs &rest body)
  `(let ((this) ,@letargs)
     (setq this ,@(last body))
     ,@(butlast body)
     (lambda (&rest params)
       (apply this params))))

What is the difference? What can this final version do that alet% can't? alet returns lambda which will call another lambda. What it's good for? If someone could give an example of indirection use, it would be appreciated.

Mark Karpov
  • 7,499
  • 2
  • 27
  • 62
  • I'm not sure exactly what `alet%` is supposed to do. It looks sort of like `let`, but it evaluates the last *body* form first, which is very counterintuitive. E.g., `(alet% ((x 1)) (+ x x) (setq x 2))` turns into `(LET ((THIS) (X 1)) (SETQ THIS (SETQ X 2)) (+ X X) THIS)`. That doesn't seem all that useful. I'd expected that the `this` might be bound to the last variable in `let`'s list of variables. What's the purpose of `alet%`? – Joshua Taylor Jun 06 '14 at 17:20
  • I think `alet%` is only useful when the last form of its body is a lambda. In other cases it doesn't seem usable. – Mark Karpov Jun 06 '14 at 17:23
  • OK, looking at the text a bit more, I see that the intent here is that the last form is supposed to be something like a lexical closure (e.g., a lambda expression), and the forms before is are supposed to be some sort of "initialization forms". This still seems like sort of a strange way to go about it… – Joshua Taylor Jun 06 '14 at 17:23
  • The thing is, if we're already going to evaluate the last form first, and then perform some more stuff on it, we've already got a macro to do that, `prog1`. This really should have just expanded to `(let ,bindings (prog1 ,@(last body) ,@(butlast body)))`. To get the anaphoric behavior, just change that to `(let (@,bindings this) (prog1 (setq this ,@(last body)) ,@(butlast body)))`. It's always a bit confusing when authors reinvent the wheel, because it leads to unidiomatic code that's harder to comprehend. – Joshua Taylor Jun 06 '14 at 17:26

1 Answers1

1

The difference is that with indirection, you can have other code that changes what the primary function will return. Consider, e.g.,

(defun get-fn-1 ()
  (let ((fn (lambda () 42)))
    (prog1 fn
      (setq fn (lambda () 43)))))

(funcall (get-fn-1))
;=> 42

Because the variable fn is evaluated first, and then the value (a function that returns 42) is returned, when you call the result of get-fn-1, you get 42, even though you assigned a new function to fn afterward. On the other hand, if you return a function that calls fn, then later changes to fn are reflected in the returned function.

(defun get-fn-2 ()
  (let ((fn (lambda () 42)))
    (prog1 (lambda () (funcall fn))
      (setq fn (lambda () 43)))))

(funcall (get-fn-2))
;=> 43

To see where the difference is really meaningful, you need a bigger example. Here's one where you get two functions: one is a "state function" that tells you what state you're in, and another that's supposed to "cycle" you among states. It's buggy though:

(defun get-state-fn-buggy ()
  (let* ((state-fns (list (lambda () 'state-1)
                         (lambda () 'state-2)
                         (lambda () 'state-3)))
         (state-fn (first state-fns)))
    ;; make the list circular, and then return as multiple values a
    ;; state function, and a function to cycle the state functions.
    (nconc state-fns state-fns)
    (values state-fn 
            (lambda () (setq state-fn (pop state-fns))))))
(multiple-value-bind (state cycle)
    (get-state-fn-buggy)
  (list (funcall state)
        (progn (funcall cycle) (funcall state))
        (progn (funcall cycle) (funcall state))
        (progn (funcall cycle) (funcall state))))
;=> (state-1 state-1 state-1 state-1)
; uh oh, we wanted different states

A better implementation returns a new function using indirection as the state function, so that "behind the scenes" modifications to state-fn are reflected in the state function that we get.

(defun get-state-fn ()
  (let* ((state-fns (list (lambda () 'state-1)
                          (lambda () 'state-2)
                          (lambda () 'state-3)))
         (state-fn (first state-fns)))
    ;; make the list circular, and then return as multiple values a
    ;; state function, and a function to cycle the state functions.
    (nconc state-fns state-fns)
    (values (lambda () (funcall state-fn))
            (lambda () (setq state-fn (pop state-fns))))))
(multiple-value-bind (state cycle)
    (get-state-fn)
  (list (funcall state)
        (progn (funcall cycle) (funcall state))
        (progn (funcall cycle) (funcall state))
        (progn (funcall cycle) (funcall state))))
;=> (state-1 state-1 state-2 state-3)
; good, now cycle has an effect on state
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • I've understood the difference, but still can hardly imagine practical benefits.. – Mark Karpov Jun 06 '14 at 17:41
  • @Mark I just expanded with a slightly more useful example. I agree that it seems kind of contrived. I think this is one of those cases where it's hard to imagine a use case until you've actually got one. Note that the author does go on to provide a few more specialized examples. `alet` may just be a "jumping-off" point for some more discussion. – Joshua Taylor Jun 06 '14 at 17:43
  • It's less vague now. Quite intricate thing.. Trick with circular list and popping is elegant, thanks! – Mark Karpov Jun 06 '14 at 17:51
  • May be it would be good thing to create chat room about Lisp. None of existing ones is devoted to Lisp. I'm curious about your thoughts of Lisp as 'first-language', I mean, for educational purposes. – Mark Karpov Jun 06 '14 at 18:01
  • @Mark There's a question in [tag:scheme] right now about continuations, [call-with-current-continuation - state saving concept](http://stackoverflow.com/q/24183566/1281433), case where indirection is needed. A function is returned that simply calls the value of a lexical variable (that gets redefined behind the scenes). It's an implementation of a generator/iterator, so it's actually sort of practical. It's not Common Lisp, but you could use the same approach in Common Lisp. – Joshua Taylor Jun 12 '14 at 19:52