Scheme has named loops, clojure has recur, and many other languages have continue for their loops, do any of you know how I might go about implementing an similar type of loop in Arc?
ccc seems like it would offer some hope, but I'm not experienced enough with continuations to be able to implement such a thing thus far. Any help?
So throw serves to "point back" to the current continuation, and optionally continues with a return value. (This seems to be the behavior of a simple (ccc (throw) ...) without the let; I'm not sure why it's defined this way.) The above loop returns 5 -- the continuation captured by ccc is outside of the loop, so its action is to return whatever value we say the while expression returns and continue evaluating anything after it.
This way, Arc gives you a decorator for return values. You can use this pattern to decorate any portion of your loop, to give different effects. Outside of the loop, it's like a break. Inside of the loop, it's like a continue due to how while is expanded.
As you can see, the continuation is captured around the body, but the test properly resides outside of the body, so it'll still be executed even if you continue (because, again, the default continuation is to keep on chuggin' along).
You could have a while macro encapsulate these (similar to catch) so you don't have to write
(point break
(while test
(point continue
body)))
I've been trying to think of good names for such a loop. The most descriptive words are long (e.g., continuable-while, breakable-while), but we usually recognize "esc" as "escape", so it's a short yet descriptive option. Something like
To make code show up here properly, put two spaces before each line of code and paste it here. As in:
;I used this function to add spaces so I could put it here!
(def add-spaces (xt)
(no:map [prn " " _] lines.xt))
This assumes you have it indented already. If you don't, find a text editor that indents Lisp code for you (I recommend DrRacket). The 'ppr function works reasonably well, too.
ccc is certainly way to go, but just in case: mzscheme does not use continuation passing style so the ccc can work much much slower than just a function call because it copies the stack.
Actually I didn't do the profiling so maybe there is nothing to worry about.
Ah, that helps understand ccc. I've been mulling ccc for months without having a handle on it. But seeing a simple solution to a simple problem I can relate to helps immeasurably.
Let's go ahead and define afor because it will be less awkward to demo with:
; s/loop/aloop/ was the only change from for's definition
(mac afor (v init max . body)
(w/uniq (gi gm)
`(with (,v nil ,gi ,init ,gm (+ ,max 1))
(aloop (assign ,v ,gi) (< ,v ,gm) (assign ,v (+ ,v 1))
,@body))))
And now check it out!
; prints i from zero to ten, skipping odd values of i
arc> (afor i 0 10
(if (odd i)
(continue)
(pr i)))
*** redefining continue
0246810nil
(Note that since the continue macro gets generated anew on each call to aloop, you'll get the "redefining continue" message every time you call aloop or afor.) You could use the above definitions to spin off labeled versions of down, forlen, each and on, and a similar technique could be applied to redefine the while-family of macros.
I'm pretty sure the second (continue) form is expanded after the inner (afor ...) form is expanded, so that it tries to continue the wrong loop, but ends up referring to an unbound variable instead.
I recommend sticking to an anaphoric variable that has no macro wrapping it, sort of like this:
I'm pretty sure the second (continue) form is expanded after the inner (afor ...) form is expanded, so that it tries to continue the wrong loop, but ends up referring to an unbound variable instead.
Very good point, I hadn't tried to nest them before.
I will try your aloop definition when I get the chance.
It sounds like you might be pleased to hear of an Arc utility variously known as 'afnwith, 'loop, or 'xloop: http://awwx.ws/xloop0
The workings behind 'xloop are pretty simple. Just like (with (a 1 b 2) c) expands into ((fn (a b) c) 1 2), (xloop (a 1 b 2) c) expands into ((rfn next (a b) c) 1 2).
Thanks, but next in an xloop doesn't really work as a substitute, right? The code after a (next) still gets executed after the recursion completes, whereas with a continue, the rest of the branch isn't executed. I really need the latter.
(I wrote this response before I read some of the more recent comments, so it'll end up duplicating information, but I'm posting it anyway 'cause of the example utilities.)
In that case, I typically use 'catch or 'point (which indeed use continuations). If you want a loop with the ability to either break or continue, you can put a 'point on the outside and another 'point on the inside.
Here's a half-effort to abstract this away. I don't know how useful it'll be, but it might be the start of something better, so everyone, feel free to use it for anything without credit. ^_^
(mac w/breaks (break next loop-header . body)
(w/uniq g-breakpoint
`(point ,g-breakpoint
(let ,break (fn ((o val)) (,g-breakpoint val))
(,@loop-header
(point ,next
,@body))))))
(mac forever body
`(while t ,@body))
(mac bforever body
`(w/breaks break next (forever) ,@body))
(mac bfor (var start end . body)
`(w/breaks break next (for ,var ,start ,end) ,@body))
; What a name.
(mac beach (var coll . body)
`(w/breaks break next (each ,var ,coll) ,@body))
; Turn any loop into a breakable one. Example usage:
;
; (b-:until (all has-come-home cows) :
; (tip rand-elt.cows)
; (unless whales
; (next))
; (actually-beach rand-elt.whales)
; (when cops
; (prn "Uh, oops?")
; (break)))
;
(mac b- (loop)
(let (header (_ . body)) (split loop (pos ': loop))
`(w/breaks break next ,header ,@body)))
(Clearly, this wouldn't mix very well with 'xloop; since an 'xloop doesn't loop unless you make it loop, a "continue" continuation wouldn't really make any sense.)