Arc Forumnew | comments | leaders | submitlogin
5 points by garply 5210 days ago | link | parent

How about something like this?

  (mac xwhile (test . body) 
    `(while ,test (ccc (fn (continue) ,@body))))
Then:

  (let x 0 
    (xwhile (< x 10) 
      (when odd.x 
        (++ x) 
        (continue)) 
      (prn x) 
      (++ x)))
does what I want it to do.

P.S. How do I format this with proper indentation on this forum?

Edit: Thanks waterhouse



5 points by fallintothis 5210 days ago | link

Looks good. That's what I would've done, if you hadn't beaten me to it. :)

Relevant arc.arc definitions:

  (mac point (name . body)
    (w/uniq (g p)
      `(ccc (fn (,g)
              (let ,name (fn ((o ,p)) (,g ,p))
                ,@body)))))

  (mac catch body
    `(point throw ,@body))
Thus,

  (catch
    (while t
      (throw 5)))
expands into

  (point throw
    (while t
      (throw 5)))
which expands (modulo gensyms) into

  (ccc (fn (current-continuation)
         (let throw (fn ((o return-val)) (current-continuation return-val))
           (while t
             (throw 5)))))
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.

  arc> (load "macdebug.arc") ; see http://www.arclanguage.org/item?id=11806
  nil
  arc> (macsteps '(while test
                    (point continue body1 body2 body3))
                 'show 'while 'point)
  Showing only: while, point

  Expression:

    (while test
      (point continue body1 body2 body3))

  Macro Expansion:

      (while test
        (point continue body1 body2 body3))

  ==> ((rfn gs2027 (gs2028)
         (when gs2028
           (point continue body1 body2 body3)
           (gs2027 test)))
       test)

  Expression:

    ((rfn gs2027 (gs2028)
       (when gs2028
         (point continue body1 body2 body3)
         (gs2027 test)))
     test)

  Macro Expansion:

      (point continue body1 body2 body3)

  ==> (ccc (fn (gs2029)
             (let continue (fn ((o gs2030)) (gs2029 gs2030))
               body1
               body2
               body3)))

  Expression:

    ((rfn gs2027 (gs2028)
       (when gs2028
         (ccc (fn (gs2029)
                (let continue (fn ((o gs2030)) (gs2029 gs2030))
                  body1
                  body2
                  body3)))
         (gs2027 test)))
     test)

  nil
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

  (mac esc-while (test . body)
    `(point break (while ,test (point continue ,@body))))
or, crafting a portmanteau à la whilet,

  (mac whilesc (test . body)
    `(point break (while ,test (point continue ,@body))))
Don't know if this actually made continuations any clearer, but there you go.

-----

3 points by waterhouse 5210 days ago | link

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.

-----

3 points by zhtw 5207 days ago | link

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.

-----

3 points by fallintothis 5207 days ago | link

  arc> ; with ccc
  (time
    (with (x 0 y 0)
      (while (< x 1000000)
        (catch
          (when (odd x)
            (++ x)
            (throw))
          (++ y)
          (++ x)))
      (list x y)))
  time: 27208 msec.
  (1000000 500000)

  arc> ; without ccc
  (time
    (with (x 0 y 0)
      (while (< x 1000000)
        (if (odd x)
            (++ x)
            (do (++ y)
                (++ x))))
      (list x y)))
  time: 1978 msec.
  (1000000 500000)
Interesting observation!

-----

2 points by akkartik 5207 days ago | link

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.

-----

1 point by evanrmurphy 5210 days ago | link

Much more elegant and less buggy than my attempt. Nice work.

-----