There was an error in my first implementation of the
loop macro in extracting the "variables" associated with the
loop bindings. I changed this in the first post (above), in case any reader gets TL;DR-itis and doesn't make it this far into the discussion.
newLISP Let Bindings
Before we get into describing the error, I should give some context.
newLISP does something very cool with
let bindings. In newLISP, you can code the
let bindings as a list of pairs -- as it is done in Common Lisp or Scheme, for example -- as in the following.
Code: Select all
> (let ((x 1) (y 2) (z 3)) (list x y z))
(1 2 3)
Alternatively, newLISP allows you to drop the pair parentheses in the
let bindings, or to mix and match.
Code: Select all
> (let (x 1 y 2 z 3) (list x y z))
(1 2 3)
> (let (x 1 (y 2) z 3) (list x y z))
(1 2 3)
Also, note how the following bindings are equivalent.
Code: Select all
> (let (x 1 (y) z 3) (list x y z))
(1 nil 3)
> (let (x 1 (y nil) z 3) (list x y z))
(1 nil 3)
The Error
So now on to how the error was introduced. I knew my code needed to build a list of "parameters" from the bindings provided by the user (caller) of
loop. These parameters are a list of all the variables in the
loop bindings, and the
loop macro was going to use these in building its call to
loop-. This list is the second argument to
loop-, by the way.
I had thought that the users of
loop would naturally need to express the
loop bindings in the same way that they express any
let bindings that they ever code. So, in building that list of parameters, I had to be mindful of the different ways that
let bindings can be expressed in newLISP (as we covered above).
The error is contained in the following (original and erroneous) definition of
loop. You might be able to spot it right away.
Code: Select all
(define-macro (loop INIT)
(letn (.parms (map first (explode (flat INIT) 2))
.body-fn (letex ([body] (args)
[parms] .parms)
(append '(fn [parms]) '[body]))
.loop-call (letex ([body-fn] .body-fn
[parms] .parms)
(append '(loop- [body-fn]) '[parms])))
(letex ([init] INIT [loop-call] .loop-call)
(letn [init] [loop-call]))))
Specifically the error is in the way the parameters,
.parms, are getting computed by the expression
(map first (explode (flat INIT) 2)). The problem is that
flat flattens the list "too deeply" for our use.
For instance, the first usage is OK, but the second breaks.
Code: Select all
> (let (INIT '(x 1 y 2 z 3)) (map first (explode (flat INIT) 2)))
(x y z)
> (let (INIT '(x 1 y (+ 40 2) z 3)) (map first (explode (flat INIT) 2)))
(x y 40 z)
Oops, look at the second usage above: 40 is not supposed to be a parameter. The second usage breaks because
flat is "too eager" or "too deep." Let's look at what
flat does to the bindings from the second usage above.
Code: Select all
> (let (INIT '(x 1 y (+ 40 2) z 3)) (flat INIT))
(x 1 y + 40 2 z 3)
Yeah, that's not what we want. What we need, however, is a "shallower" version of
flat.
The following function
flat-shallow-pairs attempts to do just that. It will flatten a list, making "flat pairs" along the way, but will respect the pairs that are explicitly expressed with parentheses.
Code: Select all
(define (flat-shallow-pairs LIST)
(let (i 0 acc '())
(dolist (e LIST)
(cond ((even? i) ; Indicator i is even = abscissa
(cond ((and (list? e) (not (empty? e)))
(extend acc (0 2 (push nil e -1))))
((symbol? e)
(push e acc -1)
(inc i))))
((odd? i) ; Indicator i is odd = ordinate
(push e acc -1)
(inc i))))
acc))
Here it is in action on the (formerly problematic) second usage and beyond.
Code: Select all
> (let (INIT '(x 1 y (+ 40 2) z 3)) (flat-shallow-pairs INIT))
(x 1 y (+ 40 2) z 3)
> (let (INIT '((x 1) y (+ 40 2) z 3)) (flat-shallow-pairs INIT))
(x 1 y (+ 40 2) z 3)
> (let (INIT '((x 1) y (+ 40 2) z (lambda (x) (flat x)))) (flat-shallow-pairs INIT))
(x 1 y (+ 40 2) z (lambda (x) (flat x)))
Now, we just replace
flat with
flat-shallow-pairs in the expression
(map first (explode (flat INIT) 2)), but we'll roll that expression into a function called
parms<-bindings.
Code: Select all
(define (parms<-bindings BINDINGS)
(map first (explode (flat-shallow-pairs BINDINGS) 2)))
Let's look at the old and new computation, side-by-side.
Code: Select all
> (let (INIT '(x 1 y (+ 40 2) z 3)) (map first (explode (flat INIT) 2)))
(x y 40 z)
> (let (INIT '(x 1 y (+ 40 2) z 3)) (parms<-bindings INIT))
(x y z)
So, the new definition of
loop is now the following.
Code: Select all
(define-macro (loop INIT)
(letn (.parms (parms<-bindings INIT)
.body-fn (letex ([body] (args)
[parms] .parms)
(append '(fn [parms]) '[body]))
.loop-call (letex ([body-fn] .body-fn
[parms] .parms)
(append '(loop- [body-fn]) '[parms])))
(letex ([init] INIT [loop-call] .loop-call)
(letn [init] [loop-call]))))
As before, please let me know about any errors or if things can be accomplished better. Thanks!
(λx. x x) (λx. x x)