4

I am idly exploring PicoLisp, and find myself perplexed about how to write meta-programming functions that would traditionally be handled with macros (in other lisp dialects). The biggest source of concern for me is that I do not see how I can prevent variable name shadowing. Reviewing the examples in Metaprogramming 101 has, if anything, just left me more confused.

Examples on how to implement the function mapeach, as seen in the linked article:

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

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

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

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

I have tested each of these with the call (let "Args" * (mapeach N (1 2 3) ("Args" N N))). As expected, the PicoLisp interpreter (started with the command pil +) experiences a segfault and crashes. I assume this is because mapeach's "Args" shadows the "Args" defined at call point.

I also tried both of their implementations of map@ (the "cuter" alternative to mapeach).

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

I used (let "Args" * (map@ (1 2 3) ("Args" @ @))) to test each of those implementations. Bizarrely, the first time I tested the first implementation, not only did it not segfault, it actually produced the correct result (1 4 9). Each subsequent test has resulted in a segfault. For clarity, the snippet from the prompt:

:  (de map@ "Args"
      (mapcar
         '(("E") (and "E" (run (cdr "Args"))))  # 'and' sets '@'
         (eval (car "Args")) ) )
-> map@
: (let "Args" * (mapeach N (1 2 3) ("Args" N N)))
!? (mapeach N (1 2 3) ("Args" N N))
mapeach -- Undefined
?                                                
: (let "Args" * (map@ (1 2 3) ("Args" @ @)))     
-> (1 4 9)

I believe that the segfault was somehow prevented by the call to the (then) undefined function mapeach, I also tried (ooga booga), which similarly prevented the segfault. If I do not have the erroneous call separating the definition from the proper call, a segfault always occurs.

This ultimately culminates in 2 questions:

  1. How can I prevent name shadowing? Clearly the examples do not succeed in that regard.
  2. Why does that call to map@ not result in a segfault?
Community
  • 1
  • 1
Charlim
  • 521
  • 4
  • 12
  • 1
    Maybe using anonymous (transient) symbols? (https://software-lab.de/doc/ref.html#symbol) – coredump Apr 16 '19 at 07:50
  • 1
    That's a part of my confusion, `"Args"` *is* a transient symbol. I tried generating a function that used an anonymous transient symbol for it's argument list, and it didn't really work. However, I think a better understanding of transient symbols may be key to solving my problem. I believe the answer is that "The index for transient symbols is cleared automatically before and after loading a source file, or it can be reset explicitly with the ==== function". It does not seem to automatically reset during ordinary REPL use. https://software-lab.de/doc/ref.html#transient-io – Charlim Apr 16 '19 at 17:01

1 Answers1

4

According to this "The index for transient symbols is cleared automatically before and after loading a source file, or it can be reset explicitly with the ==== function". It doesn't specify any way that it is automatically cleared during regular REPL usage, which is the context in which I was testing this.

This code runs properly:

[de mapeach "Args"    # expression
      (let [(@Var @List . @Body)  "Args"]
         (macro
            (mapcar
               '((@Var) . @Body)
               @List ]
(====)
(let "Args" * (mapeach N (1 2 3) ("Args" N N)))

It also runs as expected without the call to ====, but only if the call to mapeach is not in the same file.

To address the 2 parts of my question:

  1. You can prevent name shadowing by using transient symbols either in different files, or followed by a call to ====.
  2. Those calls likely worked because the debugger clears the index which contains the transient symbols.
Charlim
  • 521
  • 4
  • 12