Metaprogramming 101

Rewriting Common Lisp 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"
      (let [(@Var @Lst . Body)  "Args"]
         (macro
            (mapcar
               '((@Var) ^ Body)
               @Lst )

It works great for single variable expressions.

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

Multiple vars will break it, however.

Variations

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)

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

09dec20    erik