From: Tel A.
Subject: Lexical functions, loop variables, and all the stuff in between.
Date: 
Message-ID: <1141860627.860536.79700@j33g2000cwa.googlegroups.com>
Hi

I'm trying to write a macro to control a countdown. I want the syntax
to be something like so...

> (countdown (i 60 15)
    (format t "~a~%" i))

60
45
30
15
...

Additionally, I would like there to be a few functions available only
within the body of the macro such as reset-timer, increment-timer, and
even set-timer.

Write now I have it written like so

(defmacro countdown ((var start &optional (interval 1)) &body body)
  `(loop for ,var downfrom ,start to 0 by ,interval
         do (progn
	      ,@body)
         until (zerop ,var)
         do (sleep ,interval)))

How can I label the new functions so that they can affect
,variable-to-bind within the context of the loop body and without
rewriting them for every loop iteration?

Thanks.

From: Mikalai
Subject: Re: Lexical functions, loop variables, and all the stuff in between.
Date: 
Message-ID: <1141863510.564326.292980@j33g2000cwa.googlegroups.com>
Tel A. wrote:
---snip---
> Additionally, I would like there to be a few functions available only
> within the body of the macro such as reset-timer, increment-timer, and
> even set-timer.
>
> Write now I have it written like so
>
> (defmacro countdown ((var start &optional (interval 1)) &body body)
>   `(loop for ,var downfrom ,start to 0 by ,interval
>          do (progn
> 	      ,@body)
>          until (zerop ,var)
>          do (sleep ,interval)))
>
> How can I label the new functions so that they can affect
> ,variable-to-bind within the context of the loop body and without
> rewriting them for every loop iteration?
>
> Thanks.

Use flet or labels (
http://www.lisp.org/HyperSpec/Body/speope_fletcm_scm_macrolet.html#flet
). Something like:

(defmacro countdown ((var start &optional (interval 1)) &body body)
   `(flet  ((your-func1 ...) (your-func2 ...))
      your-loop)

Then your funcs can be used during run-time.
From: Tel A.
Subject: Re: Lexical functions, loop variables, and all the stuff in between.
Date: 
Message-ID: <1141870986.827461.303410@p10g2000cwp.googlegroups.com>
I was planning on using labels, yes; however, the problem comes in when
trying to be assured that the variable i from inside the loop is
available to the functions. I want these functions to be able to modify
the count.

The best way I've been able to write it so that it works is like so:

(defmacro countdown ((variable-to-bind start-time &optional (interval
1)) &body body)
  `(loop for ,variable-to-bind downfrom ,start-time to 0 by ,interval
	  do (labels ((timer-set (v)
		       (setf ,variable-to-bind v))
		     (timer-reset ()
		       (setf ,variable-to-bind ,start-time))
		     (timer-increment (&optional (a 1))
		       (incf ,variable-to-bind a))
		     (timer-decrement (&optional (a 1))
		       (decf ,variable-to-bind a)))
	       ,@body)
	  until (zerop ,variable-to-bind)
	  do (sleep ,interval)))

But this is problematic (in my opinion) in that the functions are
redeclared each time the loop iterates. That is the problem I am trying
to avoid.
From: Barry Margolin
Subject: Re: Lexical functions, loop variables, and all the stuff in between.
Date: 
Message-ID: <barmar-91B3C4.00332909032006@comcast.dca.giganews.com>
In article <························@p10g2000cwp.googlegroups.com>,
 "Tel A." <············@gmail.com> wrote:

> I was planning on using labels, yes; however, the problem comes in when
> trying to be assured that the variable i from inside the loop is
> available to the functions. I want these functions to be able to modify
> the count.
> 
> The best way I've been able to write it so that it works is like so:
> 
> (defmacro countdown ((variable-to-bind start-time &optional (interval
> 1)) &body body)
>   `(loop for ,variable-to-bind downfrom ,start-time to 0 by ,interval
> 	  do (labels ((timer-set (v)
> 		       (setf ,variable-to-bind v))
> 		     (timer-reset ()
> 		       (setf ,variable-to-bind ,start-time))
> 		     (timer-increment (&optional (a 1))
> 		       (incf ,variable-to-bind a))
> 		     (timer-decrement (&optional (a 1))
> 		       (decf ,variable-to-bind a)))
> 	       ,@body)
> 	  until (zerop ,variable-to-bind)
> 	  do (sleep ,interval)))
> 
> But this is problematic (in my opinion) in that the functions are
> redeclared each time the loop iterates. That is the problem I am trying
> to avoid.

I don't think you can use LOOP for this, because it doesn't provide any 
way to define its own local functions the way it provides its own local 
variables.  You'll need to bind and update the variable in your own code.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
*** PLEASE don't copy me on replies, I'll read them in the group ***
From: Wade Humeniuk
Subject: Re: Lexical functions, loop variables, and all the stuff in between.
Date: 
Message-ID: <6fPPf.15464$Cp4.9060@edtnps90>
Tel A. wrote:
> I was planning on using labels, yes; however, the problem comes in when
> trying to be assured that the variable i from inside the loop is
> available to the functions. I want these functions to be able to modify
> the count.
> 
> The best way I've been able to write it so that it works is like so:
> 
> (defmacro countdown ((variable-to-bind start-time &optional (interval
> 1)) &body body)
>   `(loop for ,variable-to-bind downfrom ,start-time to 0 by ,interval
> 	  do (labels ((timer-set (v)
> 		       (setf ,variable-to-bind v))
> 		     (timer-reset ()
> 		       (setf ,variable-to-bind ,start-time))
> 		     (timer-increment (&optional (a 1))
> 		       (incf ,variable-to-bind a))
> 		     (timer-decrement (&optional (a 1))
> 		       (decf ,variable-to-bind a)))
> 	       ,@body)
> 	  until (zerop ,variable-to-bind)
> 	  do (sleep ,interval)))
> 

Try this, (though the labels approach above will cause no problems, the compiler
will efficiently handle it).

(defmacro countdown ((variable-to-bind start-time &optional (interval 1)) &body body)
   `(macrolet ((timer-set (v) `(setf ,',variable-to-bind ,v))
               (timer-reset () `(setf ,',variable-to-bind ,',start-time))
               (timer-increment (&optional (a 1)) `(incf ,',variable-to-bind ,a))
               (timer-decrement (&optional (a 1)) `(decf ,',variable-to-bind ,a)))
      (loop for ,variable-to-bind downfrom ,start-time to 0 by ,interval
            do ,@body
            until (zerop ,variable-to-bind)
            do (sleep ,interval))))

CL-USER 8 > (pprint (macroexpand '(countdown (time 10 1)
                                     (when t (timer-reset))
                                     (timer-increment 2))))

(MACROLET ((TIMER-SET (V) `(SETF TIME ,V))
            (TIMER-RESET () '(SETF TIME 10))
            (TIMER-INCREMENT (&OPTIONAL (A 1)) `(INCF TIME ,A))
            (TIMER-DECREMENT (&OPTIONAL (A 1)) `(DECF TIME ,A)))
   (LOOP FOR TIME DOWNFROM 10 TO 0 BY 1
         DO (WHEN T (TIMER-RESET))
            (TIMER-INCREMENT 2)
         UNTIL (ZEROP TIME)
         DO (SLEEP 1)))

CL-USER 9 >
From: Pascal Bourguignon
Subject: Re: Lexical functions, loop variables, and all the stuff in between.
Date: 
Message-ID: <87bqwgw27s.fsf@thalassa.informatimago.com>
"Tel A." <············@gmail.com> writes:

> I was planning on using labels, yes; however, the problem comes in when
> trying to be assured that the variable i from inside the loop is
> available to the functions. I want these functions to be able to modify
> the count.
>
> The best way I've been able to write it so that it works is like so:
>
> (defmacro countdown ((variable-to-bind start-time &optional (interval
> 1)) &body body)
>   `(loop for ,variable-to-bind downfrom ,start-time to 0 by ,interval
> 	  do (labels ((timer-set (v)
> 		       (setf ,variable-to-bind v))
> 		     (timer-reset ()
> 		       (setf ,variable-to-bind ,start-time))
> 		     (timer-increment (&optional (a 1))
> 		       (incf ,variable-to-bind a))
> 		     (timer-decrement (&optional (a 1))
> 		       (decf ,variable-to-bind a)))
> 	       ,@body)
> 	  until (zerop ,variable-to-bind)
> 	  do (sleep ,interval)))
>
> But this is problematic (in my opinion) in that the functions are
> redeclared each time the loop iterates. That is the problem I am trying
> to avoid.

Indeed, as written, it may not work because LOOP is allowed to
generate either something like:

(let ((,variable ,initial-value))
 (tagbody
   :again
     (progn ,@body)
     (incf ,variable ,increment)
     (go :again)))

or something like:

(let ((,temp ,initial-value))
 (tagbody
   :again
     (let ((,variable ,temp)) ,@body)
     (incf ,temp ,increment)
     (go :again)))
   
So you cannot modify the iteration variable in the body of the loop
and be assured to get the same results on every implementation.




The UNTIL (ZEROP ,VARIABLE-TO-BIND) is ineffective:

[13]> (countdown (i 60 1) (timer-decrement 10) (format t "~a~%" i))
50
39
28
17
6
-5   ; !!!
NIL
[14]> 



Everytime LOOP is used in a macro, it should have a name:

   `(loop :named ,(gensym) ...)

Otherwise, people would have problems writing:

    (loop repeat 4 do (countdown (i 60 1) (when (abort-p) (return 42))))

Without the NAMED, the RETURN would refer to the innermost loop, the
one that's hidden in COUNTDOWN...




Otherwise, don't be affraid for the LABELS inside the loop: the
functions are compiled only once, and the only cost, is that of
creating a closure, that is, O(number of free-variables) = O(1).
See for example line 27 below:

[14]> (disassemble (compile nil (lambda () 
          (countdown (i 60 1) (timer-decrement 10) (format t "~a~%" i)))))

Disassembly of function NIL
(CONST 0) = 60
(CONST 1) = #<COMPILED-FUNCTION TIMER-DECREMENT>
(CONST 2) = 10
(CONST 3) = #<COMPILED-FUNCTION #:|1|>
(CONST 4) = *STANDARD-OUTPUT*
(CONST 5) = 1
(CONST 6) = SLEEP
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
reads special variable: *STANDARD-OUTPUT*
29 byte-code instructions:
0     (NIL)
1     (MAKE-VECTOR1&PUSH 1)
3     (CONST 0)                           ; 60
4     (STOREC 0 0)
7     (JMP L20)
9     L9
9     (CONST&PUSH 5)                      ; 1
10    (CALL1 6)                           ; SLEEP
12    (LOADC&PUSH 0 0)
15    (CALLS2 152)                        ; 1-
17    (STOREC 0 0)
20    L20
20    (LOADC&PUSH 0 0)
23    (CALLS2&JMPIF 148 L50)              ; MINUSP
26    (LOAD&PUSH 0)
27    (COPY-CLOSURE&PUSH 1 1)             ; #<COMPILED-FUNCTION TIMER-DECREMENT>
30    (CONST&PUSH 2)                      ; 10
31    (LOAD 1)
32    (CALLC)
33    (CONST&PUSH 3)                      ; #<COMPILED-FUNCTION #:|1|>
34    (GETVALUE&PUSH 4)                   ; *STANDARD-OUTPUT*
36    (LOADC&PUSH 3 0)
39    (CALLSR 2 21)                       ; FUNCALL
42    (SKIP 1)
44    (LOADC&PUSH 0 0)
47    (CALLS2&JMPIFNOT 146 L9)            ; ZEROP
50    L50
50    (NIL)
51    (SKIP&RET 2)
NIL
[15]> 



In conclusion, in the case of your COUNTDOWN macro, it would be better
to use lower level constructs than LOOP:  LET, LABELS, and TAGBODY.
Perhaps you could even do without the functions, why not allow:


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

THIS IS A 100% MATTER PRODUCT: In the unlikely event that this
merchandise should contact antimatter in any form, a catastrophic
explosion will result.
From: Mikalai
Subject: Re: Lexical functions, loop variables, and all the stuff in between.
Date: 
Message-ID: <1141922436.499591.11860@p10g2000cwp.googlegroups.com>
Tel A. wrote:
> I was planning on using labels, yes; however, the problem comes in when
> trying to be assured that the variable i from inside the loop is
> available to the functions. I want these functions to be able to modify
> the count.
>
> The best way I've been able to write it so that it works is like so:
>
> (defmacro countdown ((variable-to-bind start-time &optional (interval
> 1)) &body body)
>   `(loop for ,variable-to-bind downfrom ,start-time to 0 by ,interval
> 	  do (labels ((timer-set (v)
> 		       (setf ,variable-to-bind v))
> 		     (timer-reset ()
> 		       (setf ,variable-to-bind ,start-time))
> 		     (timer-increment (&optional (a 1))
> 		       (incf ,variable-to-bind a))
> 		     (timer-decrement (&optional (a 1))
> 		       (decf ,variable-to-bind a)))
> 	       ,@body)
> 	  until (zerop ,variable-to-bind)
> 	  do (sleep ,interval)))
>
> But this is problematic (in my opinion) in that the functions are
> redeclared each time the loop iterates. That is the problem I am trying
> to avoid.

OK. Let's analyse it.
a) You want func (timer-reset, example) to close over a variable
(,variable-to-bind). You want a closure.
b) If the variable is introduced by loop (internally it uses let,
etc.), then your funcs has to be inside the loop. But you want them
outside the loop.
c) To have functions outside the loop, you have to "let" your variable
outside the loop as well. Then you flet your functions. Then you write
the loop (it can be do).
d) If you do like in (c), it may happen, that you won't be able to use
loop's syntactic sugar to define variable, increment it, whatever. If
encrementation, etc. are needed, you have to write the code explicitly
in the loop before or after the body. I even suggest to write first
just a loop - infinite with no exit, and later introduce luxaries of a
loop's syntax, where they will be needed, and only there!
 This is the way to have functions defined only once.

 The moral:
 Any syntax sugar, like loop, is not infinitly flexible. And sometimes
they get in the way.