Как написать функцию PicoLisp, которая не затеняет переменные своими параметрами

#scope #lisp #dynamic-scope #picolisp

#область действия #lisp #динамическая область видимости #picolisp

Вопрос:

Я лениво изучаю PicoLisp и нахожусь в недоумении по поводу того, как писать функции метапрограммирования, которые традиционно обрабатывались бы с помощью макросов (на других диалектах lisp). Самым большим источником беспокойства для меня является то, что я не вижу, как я могу предотвратить затенение имени переменной. Просмотр примеров в Metaprogramming 101, во всяком случае, только еще больше запутал меня.

Примеры реализации функции mapeach , как показано в связанной статье:

    [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") ) ) )
 

Я протестировал каждый из них с помощью вызова (let "Args" * (mapeach N (1 2 3) ("Args" N N))) . Как и ожидалось, интерпретатор PicoLisp (запущенный с помощью команды pil ) испытывает сбой segfault и сбой. Я предполагаю, что это связано mapeach с "Args" тем, что затеняет "Args" определенное в точке вызова.

Я также попробовал обе их реализации map@ («более привлекательную» альтернативу 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")) ) )
 

Раньше я (let "Args" * (map@ (1 2 3) ("Args" @ @))) тестировал каждую из этих реализаций. Как ни странно, в первый раз, когда я тестировал первую реализацию, она не только не segfault, но и фактически выдала правильный результат (1 4 9) . Каждый последующий тест приводил к segfault. Для наглядности фрагмент из приглашения:

 :  (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)
 

Я считаю, что ошибка segfault была каким-то образом предотвращена вызовом (тогда) неопределенной функции mapeach , я также пытался (ooga booga) , что аналогичным образом предотвратило ошибку segfault. Если у меня нет ошибочного вызова, отделяющего определение от правильного вызова, всегда возникает ошибка segfault.

В конечном итоге это приводит к 2 вопросам:

  1. Как я могу предотвратить затенение имени? Очевидно, что примеры не преуспевают в этом отношении.
  2. Почему этот вызов map@ не приводит к segfault ?

Комментарии:

1. Возможно, с использованием анонимных (временных) символов? ( software-lab.de/doc/ref.html#symbol )

2.Это часть моего замешательства, "Args" это временный символ. Я попытался сгенерировать функцию, которая использовала анонимный переходный символ для своего списка аргументов, и это действительно не сработало. Однако я думаю, что лучшее понимание переходных символов может быть ключом к решению моей проблемы. Я полагаю, что ответ заключается в том, что «индекс для переходных символов очищается автоматически до и после загрузки исходного файла, или его можно сбросить явно с помощью функции ====». Похоже, что он не сбрасывается автоматически при обычном использовании REPL. software-lab.de/doc/ref.html#transient-io

Ответ №1:

В соответствии с этим «индекс для переходных символов автоматически очищается до и после загрузки исходного файла, или его можно сбросить явно с помощью функции ====». В нем не указано, каким образом он автоматически очищается при обычном использовании REPL, что является контекстом, в котором я это тестировал.

Этот код выполняется правильно:

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

Он также выполняется, как и ожидалось, без вызова ==== , но только если вызов mapeach не находится в том же файле.

Чтобы ответить на 2 части моего вопроса:

  1. Вы можете предотвратить затенение имени, используя переходные символы либо в разных файлах, либо с последующим вызовом ==== .
  2. Эти вызовы, вероятно, сработали, потому что отладчик очищает индекс, содержащий переходные символы.