How to implement a redo statement (both in Perl and Ruby) in Lisp

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

If Lisp had the equivalent of redo , I think it will work as follows: a special form with-redo that takes a character and forms, and redo that takes a character. A form (with-redo 'foo BODY-FORMS...) can contain (redo 'foo) in its BODY-FORMS, and (redo 'foo) transfers control back to the beginning of BODY-FORMS.

+4
source share
3 answers

In general, Lisp:

 (tagbody start (do-something) (go start)) (dotimes (i some-list) redo (when (some-condition-p) (go redo)) (some-more)) 
+7
source

Rainer's answer illustrates the use of tagbody , which is probably the easiest way to implement this type of construct (a special type of goto , or an unconditional jump). I thought it would be nice to note that if you do not want to use the explicit tag or the implicit tag provided by one of the standard constructs, you can also create with-redo in the same way as you suggested. The only difference in this implementation is that we will not specify a tag, since they are not evaluated in the tagbody , and compatibility with other constructs is also good.

 (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 fuzzy abstraction , because body can define other labels for the implicit tagbody and can use go instead of redo , etc. This may be desirable; many built-in iterative constructs (like do , do* ) use an implicit tagbody , so it might be ok, But since you also add your own control flow operator, redo , you might want to make sure that it can only be used with tags, defined with-redo . In fact, while Perl redo can be used with or without a label, Ruby redo does not seem to allow a shortcut. Unsuccessful cases allow transition behavior to the innermost closed loop (or, in our case, the innermost with-redo ). We can eliminate the fuzzy abstraction, as well as the ability for the redo jack at the same time.

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

Here we have defined a tag for use with with-redo , which other things should not be aware of (and cannot know if they do not macro-expose some forms of with-redo , and we wrapped the body in lambda , which means, for example, the character body is the form to be evaluated, not the tag for the tagbody , here is an example showing that redo returns to the nearest lexically spanning with-redo :

 CL-USER> (let ((i 0) (j 0)) (with-redo (with-redo (print (list ij)) (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 yourself, you can decide which design you want to take. You might like the idea of redo not accepting arguments (and disguising go with a secret label, but with-redo is still an implicit tag so you can define other tags and go to them with go ; may also change the code here to do this also.

Some implementation notes

This answer generated some comments, I wanted to make a couple more notes about the implementation. The implementation of with-redo with labels is pretty straight forward, and I think all the answers are posted at; without a shortcut is a little harder.

Firstly, using a local macrolet is a convenience that will lead to warnings with redo , outside of any lexically encompassing with-redo . For example, in SBCL:

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

Secondly, using #1=#:hidden-label and #1# means that the go tag for reuse is a non-integer character (which reduces the likelihood of abstraction leakage), but it is also the same character for with-redo decompositions. The following snippet tag1 and tag2 show go tags from two different with-redo extensions.

 (let* ((exp1 (macroexpand-1 '(with-redo 1 2 3))) (exp2 (macroexpand-1 '(with-redo abc)))) (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 with-redo implementation using fresh gensym for each macro extension does not have this guarantee. For example, 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 abc)))) (destructuring-bind (ml bndgs (tb tag1 &rest rest)) exp1 (destructuring-bind (ml bndgs (tb tag2 &rest rest)) exp2 (eq tag1 tag2)))) ; => NIL 

Now, is it worth asking whether this is of practical importance, and if so, in what cases, and is it the difference for the better or for the worse? Honestly, I'm not quite sure.

If you did complex code manipulation after internal macro definition of the form (with-redo ...) , form 1 so that (redo) has already been turned into (go #1#) , that means moving (go #1#) into the body of another form (with-redo ...) , form 2 , it will still have the effect of restarting the iteration in form 2 . In my opinion, this makes it more similar to return , which can be transferred from block b 1 to another block b 2 , with the only difference being that it now returns from b 2 instead of b 1 . I think this is desirable, because we are trying to consider the unmarked with-redo and redo as primitive control structures.

+5
source

Update: Emacs 24.4 (coming soon) has a tagbody. cl-lib, which comes with Emacs 24.4, includes cl-tagbody.

For a Lisp dialect that does not have a tag, you can still implement repeat if the dialect has the equivalent of catch / throw.

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

Usage example (all examples are given 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)) 

The same example is written as an average test cycle:

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

The same example is 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)))) 

The implementation of with-lex-redo-anon and lex-redo , where (lex-redo) leads to the beginning of the body of the text / lexically hidden form with-lex-redo-anon :

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

Test example:

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

The same conclusion as in the other answer.

+1
source

Source: https://habr.com/ru/post/1488645/


All Articles