Arc Forumnew | comments | leaders | submitlogin
Let Over lambda, Ch 6 Ichain intercept
2 points by cthammett 3269 days ago | 5 comments
Good Afternoon Ladies and Gentlemen, I am trying to translate the following into arc. The macrolet conversion is where I am having difficulties.

  (defmacro! ichain-intercept (&rest body)
  `(let ((,g!indir-env this))
     (setq this
       (lambda (&rest ,g!temp-args)
         (block ,g!intercept
           (macrolet ((intercept (v)
                       `(return-from
                          ,',g!intercept
                          ,v)))
             (prog1
               (apply ,g!indir-env
                      ,g!temp-args)
               ,@body)))))))

  ;This is what I have for arc

  ;Indirection chain intercept
  (mac ichain-intercept body
    (w/uniq (indir-env temp-args intercept)
      `(with (,indir-env this)
        (assign this
          (fn ,temp-args
            (point ,intercept
              (mac intercept (v)
                    `(,',intercept
                      ,v)
                 (do1
                  (apply  ,indir-env
                          ,temp-args)
                   ,@body))))))))

  ;indirection chain intercept test
  (assign ici-test1
    (alet (acc 0)
      (ichain-intercept
        (when (< acc 0)
         (prn "Acc went negative")
         (= acc 0)
         (intercept acc)))
      (fn (n)
         (= acc (inc acc n)))))


  ;The test results should be
  (ici-test1 -8)
  Acc went negative
  0
  (ici-test1 3)
  3
Help is much appreciated. Here is other examples I have had success with in chapter 6, which I hope is useful for others like me who are learning arc.

  ;Anaphoric let
  (mac alet (args . body)
    `(with ,args
      (assign this ,(last body))
        ,@(butlast body)
          (fn params
	    (apply this params))))

  ;Alet finite state machine
  (mac alet-fsm states	 
    `(do 
      (assign ,@states)
        (mac state (s)
          `(assign this ,s))))

  ;Testings alet finite state machine
  (assign tes
    (alet (acc 0)
      (alet-fsm
        going-up
          (fn (n)
            (if (is n 'invert)
              (state going-down)
	      (= acc (inc acc n))))
        going-down
	   (fn (n)
             (if (is n 'invert)
               (state going-up)
               (= acc ( inc acc (* n -1))))))))

  ;Indirection Chain Before
  (mac ichain-before body
    (w/uniq (indir-env temp-args)
      `(with (,indir-env this)
        (assign this
          (fn ,temp-args
            ,@body
            (apply ,indir-env
                   ,temp-args))))))

  ;Indirection chain test 1
  (assign icb-test1
    (alet (acc 0)
      (ichain-before
        (prn "Changing from " acc))
	(fn (n)
           (= acc (inc acc n)))))

  ;Test values (icb-test1 2) , (icb-test1 2)

  ;Indirection chain test 2
  (assign icb-test2
    (alet (acc 0)
      (ichain-before
        (prn "A"))
      (ichain-before
        (prn "B"))
      (ichain-before
        (prn "C"))
      (fn (n)
        (= acc (inc acc n)))))

  ; Test values (icb-test 2)

  ;indirection chain before test3
  (assign icb-test3
    (alet (acc 0)
      (fn (n)
        (ichain-before
        (prn "Hello World" ))
        (= acc (inc acc n)))))

  ;start test with  (start-icbt3)
  (def start-icbt3 ()
    (for x 0 (<  x 5) (++ x)
      (do 
        (prn "Invocation "x)
        (icb-test3 x)
        (sleep 1))))

  ;Indirection chain after
  (mac ichain-after body
    (w/uniq (indir-env temp-args)
      `(with (,indir-env this)
        (assign this
          (fn ,temp-args
            (do1
              (apply ,indir-env
                     ,temp-args)
             ,@body))))))

  ;Indirection after test 1
  (assign ica-test1
    (alet (acc 0)
      (ichain-before
        (prn "Changing from " acc))
      (ichain-after
        (prn "Changing to " acc))
      (fn (n) 
        (= acc (inc acc n)))))

  ;test values (ica-test1 7), (ica-test1 7)

  ;Indirection chain intercept-1
  (mac ichain-intercept% body
    (w/uniq (indir-env temp-args)
      `(with (,indir-env this)	
        (assign this
          (fn ,temp-args
            (point intercept
              (do1
                (apply ,indir-env
                       ,temp-args)
                ,@body)))))))

  ;Indirection chain Intercept% test1
  (assign ici%-test1
    (alet (acc 0)
      (ichain-intercept%
        (when (< acc 0)
          (prn "Acc went negative")
	  (= acc 0)
          (intercept acc)))
     (fn (n)
      (= acc (inc acc n)))))

  ;test values (ici%-test1 3) ,(ici%-test1 6) ,(ici%-test1 -20)


  (mac alet-hotpatch% (args . body)
    `(with ,args
      (assign this ,(last body))
        ,@(butlast body)
          (fn args
            (if (is car.args 'hotpatch)
              (assign this cadr.args)
                (apply this args))))) 

  ;alet hotpatch% test 1
  (assign ahp%test1	
    (alet-hotpatch% (acc 0)
      (fn (n)
        (= acc (inc acc n)))))

  ;test values (ahp%test1 3) , (ahp%test1 3)
  ;(ahp%test1 'hotpatch (let acc 0 ( fn (n) (= acc (inc acc (*2 n))))))
  ;(ahp%test1 2) (ahp%test1 5)

  ;Destructured lambda
  (mac dfn ds
    (w/uniq (it)
      `(with (it nil)
        (fn it
          (case (car it)
            ,@ds)))))

  (mac alet-hotpatch (args . body)
    `(with ,args
        (assign this ,(last body))
        ,@(butlast body)
        (dfn
          hotpatch (assign this cadr.it)
	  (this car.it))))
	
  ;alet hotpatch test 1
  (assign ahp	
    (alet-hotpatch (acc 0)
      (fn (n)
        (= acc (inc acc n)))))

  ;test values
  ;(ahp 3) , (ahp 3)
  ;(ahp 'hotpatch (let acc 100 ( fn (n) (= acc (inc acc (*2 n))))))
  ;(ahp 3) , (ahp 3)


2 points by rocketnia 3269 days ago | link

I think you might have made a slight mistake translating 'alet. Here's a minimal fix:

   ;Anaphoric let
   (mac alet (args . body)
  -  `(with ,args
  +  `(with (this nil ,@args)
        (assign this ,(last body))
        ,@(butlast body)
        (fn params
          (apply this params))))
The code probably seemed to be working, but it was actually setting a variable 'this from the surrounding scope rather than its own 'this.

In a way it's fitting to bring that up, because 'alet-fsm is designed to modify the 'this from the surrounding scope....

Er, speaking of which, your translation of 'alet-fsm is pretty surprising. (You're assigning a bunch of nonlocal variables, and you're even redefining a global macro at run time, which is usually too late because the program has already been macroexpanded. Furthermore, instead of returning the first state of the FSM, you're returning the implementation of a macro.) I'll start from the original Common Lisp code:

  (defmacro alet-fsm (&rest states)
    `(macrolet ((state (s)
                  `(setq this #',s)))
       (labels (,@states) #',(caar states))))
While Arc doesn't have (macrolet ...), in this case we can achieve something very close by just making this macro global, since it has no local dependencies.

  (mac state (s)
    `(assign this ,s))
What (labels ...) does in Common Lisp is introduce some local variables that hold functions, in such a way that every function's definition can see every other function. To keep my code simple to read, I'm going to resort to a utility from Anarki that achieves the same kind of scope, namely (withr ...):

  (mac alet-fsm states
    `(withr ,states
       ,car.states))
The expansion of (withr (a (fn ...) b (fn ...) c (fn ...)) ...) is something like this:

  (with (a nil b nil c nil)
    (assign a (fn ...))
    (assign b (fn ...))
    (assign c (fn ...))
    ...)
Note how the local variables are introduced in a (with ...) scope that fully surrounds the (fn ...) expressions, so all the variables are in scope for all the functions.

-----

2 points by rocketnia 3269 days ago | link

Hmm... I haven't looked at the whole context of what you're doing. but I see you said "The macrolet conversion is where I am having difficulties." That makes sense. Arc doesn't have macrolet! (I think even Anarki doesn't have it... does it?)

Generally, I'd try to convert (macrolet ...) into local functions instead of local macros.

I don't know Common Lisp very well, but judging by the "surprising" example at http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node85.html, maybe there was no particular reason for this example to use (macrolet ...) in the first place:

   (defmacro! ichain-intercept (&rest body)
     `(let ((,g!indir-env this))
        (setq this
          (lambda (&rest ,g!temp-args)
            (block ,g!intercept
  -           (macrolet ((intercept (v)
  -                       `(return-from
  -                          ,',g!intercept
  -                          ,v)))
  +           (flet ((intercept (v)
  +                    (return-from
  +                      ,g!intercept
  +                      v)))
                (prog1
                  (apply ,g!indir-env
                         ,g!temp-args)
                  ,@body)))))))
(I haven't tested this.)

At this point, the code can be translated to Arc, and then it can be simplified dramatically:

  (point ,intercept
    (let intercept (fn (v) (,intercept v))
      ...))
  
  -->
  
  (point ,intercept
    (let intercept ,intercept
      ...))
  
  -->
  
  (point intercept
    ...)
This ends up simplifying to the exact same code as ichain-intercept%.

I think that makes sense. This example only accomplished the convenience of writing (intercept acc) instead of (return-from intercept acc), but your Arc code already had (intercept acc) to begin with.

-----

1 point by akkartik 3269 days ago | link

Remind us, what is alet? I tried reading http://letoverlambda.com/index.cl/guest/chap6.html but it uses vocabulary from previous chapters, like dlambda.

-----

2 points by rocketnia 3269 days ago | link

Hmm, looking at the implementation, 'alet is a utility for wrapping a lambda to do various things:

a) The letargs let you set up some bindings the function can refer to.

b) The body lets you do some things with the function in scope before returning it.

c) It wraps the function in a mutable binding named 'this, and it actually returns another function that dereferences this binding and calls whatever's inside. That way you can dynamically replace the entire behavior of the function by assigning to 'this.

So it seems to be geared for a certain object-oriented style where objects have a) private fields, b) a programmable constructor, and c) a private "become" operation.

It looks like 'dlambda is some kind of a multi-branch, destructuring lambda. In this context of this object-oriented style, it effectively lets your object have multiple methods.

-----

1 point by cthammett 3269 days ago | link

Thank you for the tips and corrections.

-----