Metaprogramming 101

Rewriting macros as PicoLisp functions
I came across a cute little Common Lisp macro, mapeach. Naturally, I wanted it to play with it in PicoLisp. The most straightforward translation (for me) was:

   [de mapeach "Args"    # expression
      (let [(@Var @List . @Body)  "Args"]
         (macro
            (mapcar
               '((@Var) . @Body)
               @List ]

Pretty cute in PicoLisp too :)

It works great for single variable expressions.

   : (mapeach N (1 2 3) (* N N))
   -> (1 4 9)

Multiple vars will break it, however.



There are many ways we could write 'mapeach'. How about,

   (de mapeach "Args"
      (mapcar
         (cons (cons (car "Args")) (cddr "Args"))
         (eval (cadr "Args")) ) )

The 'cons'es are a little less expensive than the full macro.

Another could be:

   (de mapeach "Args"
      (mapcar
         '(("E")
            (bind (car "Args")
               (set (car "Args") "E")
               (run (cddr "Args")) ) )
         (eval (cadr "Args")) ) )

This is a bit longer, but needs no consing at all.



And if we define this syntax for multiple variables:

   (mapeach (X Y) '(a b c) '(d e f) (foo ...

we can write

   (de mapeach "Args"
      (let "Vars" (pop '"Args")
         (apply mapcar
            (mapcar eval (cut (length "Vars") '"Args"))
            (cons "Vars" "Args") ) ) )

   : (mapeach (X Y) (1 2 3) (4 5 6)
      (* X Y) )
   -> (4 10 18)



omg, even cuter!

An even more "cute" solution would be if we could avoid the parameter argument completely. The natural way for this in PicoLisp is the implied parameter @.

We might define

   (de map@ "Args"
      (mapcar
         '(("E") (and "E" (run (cdr "Args"))))  # 'and' sets '@'
         (eval (car "Args")) ) )

With that, you can call


   : (map@ (1 2 3) (* @ @))
   -> (1 4 9)



As a side note, there's a clever use of 'and' here, which is fairly common in PicoLisp code. Always have in mind the back of your mind that 'and' sets the value of @ (just like the other "flow functions".

A more traditional way would be,

   '(("E") (let @ "E" (run (cdr "Args"))))

i.e. explicitly bind the symbol with 'let'.

But why make it so complicated? We can have our "lambda" expression directly (and dynamically) bind the @ symbol.

   '((@) (run (cdr "Args")))

With that, the whole function is

   (de map@ "Args"
      (mapcar
         '((@) (run (cdr "Args")))
         (eval (car "Args")) ) )

https://picolisp.com/wiki/?metaprogramming101

19mar16   erik