4

Code that requires break statements or continue statements in other languages can be done with block & return-from or catch & throw in Common Lisp and Emacs Lisp. Then there is code that requires redo statements, or at least best written with redo. And redo statements don't have to be about loops. How can I do redo in Lisp?

If there was a redo equivalent in Lisp, I think it would work like this: special form with-redo which takes a symbol and forms, and redo which takes a symbol. The form (with-redo 'foo BODY-FORMS...) may contain (redo 'foo) in its BODY-FORMS, and (redo 'foo) transfers control back to the beginning of BODY-FORMS.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Jisang Yoo
  • 3,670
  • 20
  • 31

3 Answers3

7

In Common Lisp:

(tagbody
   start
   (do-something)
   (go start))


(dotimes (i some-list)
  redo
  (when (some-condition-p)
    (go redo))
  (some-more))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • 5
    It should be added that some macros (like `dotimes`, or more generally all looping macros starting with `do`) implicitly enclose their bodies in a tagbody. That is what is demonstrated in the second example above. – Svante Jun 28 '13 at 13:09
5

Rainer's answer illustrates the use of tagbody which is probably the easiest way to implement this kind of construct (a particular kind of goto, or unconditional jump). I thought it'd be nice to point out that if you don't want to use an explicit tagbody, or an implicit tagbody provided by one of the standard constructs, you can also create a with-redo just as you suggested. The only difference in this implementation is that we won't quote the tag, since they're not evaluted in tagbody, and being consistent with the other constructs is nice too.

(defmacro with-redo (name &body body)
  `(macrolet ((redo (name)
                `(go ,name)))
     (tagbody
        ,name
        ,@body)))

CL-USER> (let ((x 0))
           (with-redo beginning
             (print (incf x))
             (when (< x 3)
               (redo beginning))))
1 
2 
3 
; => NIL

Now this is actually a leaky abstraction, since the body could define other labels for the implicit tagbody, and could use go instead of redo, and so on. This might be desirable; lots of the built in iteration constructs (e.g., do, do*) use an implicit tagbody, so it might be OK. But, since you're also adding your own control flow operator, redo, you might want to make sure that it can only be used with tags defined by with-redo. In fact, while Perl's redo can be used with or without a label, Ruby's redo doesn't appear to allow a label. The label-less cases allow behavior of jumping back to the innermost enclosing loop (or, in our case, the innermost with-redo). We can address the leaky abstraction, as well as the ability to nest redos at the same time.

(defmacro with-redo (&body body)
  `(macrolet ((redo () `(go #1=#:hidden-label)))
     (tagbody
        #1#
        ((lambda () ,@body)))))

Here we've defined a tag for use with with-redo that other things shouldn't know about (and can't find out unless they macroexpand some with-redo forms, and we've wrapped the body in a lambda function, which means that, e.g., a symbol in the body is a form to be evaluated, not a tag for tagbody. Here's an example showing that redo jumps back to the nearest lexically enclosing with-redo:

CL-USER> (let ((i 0) (j 0))
           (with-redo
             (with-redo 
               (print (list i j))
               (when (< j 2)
                 (incf j)
                 (redo)))
             (when (< i 2)
               (incf i)
               (redo))))

(0 0) 
(0 1) 
(0 2) 
(1 2) 
(2 2) 
; => NIL

Of course, since you can define with-redo on your own, you can make the decisions about which design you want to adopt. Perhaps you like the idea of redo taking no arguments (and disguising a go with a secret label, but with-redo still being an implicit tagbody so that you can define other tags and jump to them with go; you can adapt the code here to do just that, too.

Some notes on implementation

This this answer has generated a few comments, I wanted to make a couple more notes about the implementation. Implementing with-redo with labels is pretty straightfoward, and I think that all the answers posted address it; the label-less case is a bit tricker.

First, the use of a local macrolet is a convenience that will get us warnings with redo is used outside of some lexically enclosing with-redo. E.g., in SBCL:

CL-USER> (defun redo-without-with-redo ()
           (redo))
; in: DEFUN REDO-WITHOUT-WITH-REDO
;     (REDO)
; 
; caught STYLE-WARNING:
;   undefined function: REDO

Second, the use of #1=#:hidden-label and #1# means that the go tag for redoing is an uninterned symbol (which lessens the likelihood that the abstraction leaks), but also is the same symbol across expansions of with-redo. In the following snippet tag1 and tag2 are the go-tags from two different expansions of with-redo.

(let* ((exp1 (macroexpand-1 '(with-redo 1 2 3)))
       (exp2 (macroexpand-1 '(with-redo a b c))))
  (destructuring-bind (ml bndgs (tb tag1 &rest rest)) exp1   ; tag1 is the go-tag
    (destructuring-bind (ml bndgs (tb tag2 &rest rest)) exp2
      (eq tag1 tag2))))
; => T

An alternative implementation of with-redo that uses a fresh gensym for each macroexpansion does not have this guarantee. For instance, consider with-redo-gensym:

(defmacro with-redo-gensym (&body body)
  (let ((tag (gensym "REDO-TAG-")))
    `(macrolet ((redo () `(go ,tag)))
       (tagbody 
          ,tag
          ((lambda () ,@body))))))

(let* ((exp1 (macroexpand-1 '(with-redo-gensym 1 2 3)))
       (exp2 (macroexpand-1 '(with-redo-gensym a b c))))
  (destructuring-bind (ml bndgs (tb tag1 &rest rest)) exp1
    (destructuring-bind (ml bndgs (tb tag2 &rest rest)) exp2
      (eq tag1 tag2))))
; => NIL

Now, it's worth asking whether this makes a practical difference, and if so, in which cases, and is it a difference for the better or the worse? Quite frankly, I'm not entirely sure.

If you were performing some complicated code manipulation after the inner macroexpansion of an (with-redo ...) form, form1, so that (redo) has already been turned into (go #1#), it means that moving the (go #1#) into the body of another (with-redo ...) form, form2, it will still have the effect of restarting an iteration in form2. In my mind, this makes it more like a return that could be transported from a block b1 into a different block b2, with the only difference it now returns from b2 instead of b1. I think that this is desirable, since we're trying to treat label-less with-redo and redo as primitive control structures.

Community
  • 1
  • 1
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • 2
    Interesting use of the #: read macro and #1# to create a fresh uninterned symbol that can be referenced later. I've never seen this before. I can't decide if I like this better compared to the typical (let (foo (gensym)) `(...)) approach that I've seen much more often. Any reason why one is better/more appropriate to prevent variable capture, or is it simply a matter of style to use one or the other? – Clayton Stanley Jun 29 '13 at 07:37
  • 1
    @ClaytonStanley Using an ubintenred (read) symbol allows for good-looking code, but can cause some confusion when looking at expanded macros (if you expand this `(with-redo .... (with-redo ...) ...)` it is not clear which `#:hidden-label` is which, but using the LET idiom and `(gensym 'hidden-label)` should lead to the uninterned symbols being named different things (#:hidden-symbol0001, ...). – Vatine Jun 29 '13 at 07:47
  • 1
    @Vatine Right. That's what threw me off at first with the #:foo technique. I know that you cannot rely on print statements of symbols to determine if they are eq, but at least with the gensym technique you get some visual feedback that says that they are probably not eq. – Clayton Stanley Jun 29 '13 at 08:42
  • @Vatine @ClaytonStanley In this case though, we want the _same_ uninterned symbol across _all_ expansions of `with-redo`, so that we can reliably say that `redo` brings us back to the lexically enclosing innermost `with-redo`. An alternative would be `(let ((hidden-tag (gensym …))) (defmacro …))`, but that's got a top-level let which I find a little ugly (but it's not really a problem), or `(defvar *hidden-tag* …)`, but then we've defined something that might draw someone's attention (but that's not really a problem either; if you poke the internals, you might break something). – Joshua Taylor Jun 29 '13 at 11:59
1

Update: Emacs 24.4 (soon to be released) has tagbody. cl-lib that comes with Emacs 24.4 includes cl-tagbody.

For a dialect of Lisp which doesn't have tagbody, one can still implement redo as long as the dialect has a catch/throw equivalent.

For Emacs Lisp:

;; with-redo version 0.1
(defmacro with-redo (tag &rest body)
  "Eval BODY allowing jumps using `throw'.
TAG is evalled to get the tag to use; it must not be nil.
Then the BODY is executed.
Within BODY, a call to `throw' with the same TAG and a non-nil VALUE causes a jump to the beginning of BODY.
A call to `throw' with the same TAG and nil as VALUE exits BODY and this `with-redo'.
If no throw happens, `with-redo' returns the value of the last BODY form."
  (declare (indent 1))
  (let ((ret (make-symbol "retval")))
    `(let (,ret)
       (while
           (catch ,tag
             (setq ,ret (progn ,@body))
             nil))
       ,ret)))
(defun redo (symbol)
  (throw symbol t))

Example of use (all examples are in Emacs Lisp):

(with-redo 'question
  (let ((name (read-string "What is your name? ")))
    (when (equal name "")
      (message "Zero length input. Please try again.")
      (beep)
      (sit-for 1)
      (redo 'question))
    name))

Same example written as a mid-test loop instead:

(require 'cl-lib)
(let (name)
  (cl-loop do
           (setq name (read-string "What is your name? "))
           while
           (equal name "")
           do
           (message "Zero length input. Please try again.")
           (beep)
           (sit-for 1))
  name)

Same example written as an infinite loop with a throw instead:

(let (name)
  (catch 'question
    (while t
      (setq name (read-string "What is your name? "))
      (unless (equal name "")
        (throw 'question name))
      (message "Zero length input. Please try again.")
      (beep)
      (sit-for 1))))

Implementing with-lex-redo-anon and lex-redo, where (lex-redo) causes a jump to the beginning of body of the textually/lexically innermost with-lex-redo-anon form:

;; with-lex-redo-anon version 0.1
(require 'cl-lib)
(defmacro with-lex-redo-anon (&rest body)
  "Use with `(lex-redo)'."
  (let ((tag (make-symbol "lex-redo-tag"))
        (ret (make-symbol "retval")))
    `(cl-macrolet ((lex-redo () '(cl-return-from ,tag t)))
       (let (,ret)
         (while
             (cl-block ,tag
               (setq ,ret (progn ,@body))
               nil))
         ,ret))))

Example test:

(let ((i 0) (j 0))
  (with-lex-redo-anon
    (with-lex-redo-anon
      (print (list i j))
      (when (< j 2)
        (incf j)
        (lex-redo)))
    (when (< i 2)
      (incf i)
      (lex-redo))))

Same output as in another answer.

Jisang Yoo
  • 3,670
  • 20
  • 31
  • In Common Lisp, `catch` and `throw` have dynamic connection (`throw` just needs to happen _while_ a corresponding `catch` is higher up on the stack), whereas `tagbody` and `go` are lexical. E.g., `(flet ((foo () (go away))) (tagbody away (foo)))` is an error, but `(flet ((foo () (throw 'away))) (catch 'away (foo)))` is OK. With fresh symbols, a `catch`-based solution could work, but `redo` still needs the `tag` as an argument, which the question allowed, but is less like the label-less `redo`s of Perl and Ruby. Can this be adapted to allow a label-less `redo` that always transfers to the… – Joshua Taylor Jun 29 '13 at 12:10
  • …lexically innermost enclosing `with-redo`? – Joshua Taylor Jun 29 '13 at 12:13
  • I added a definition of `with-lex-redo-anon` to the answer. This relies on lexical `cl-block` and `cl-return-from` which are implemented in `cl-lib` by using dynamical `catch` and `throw`. Not sure how `cl-lib` pulls that off, but they seem to work. – Jisang Yoo Jun 29 '13 at 17:06