Использование локальной специальной переменной, переданной в качестве окончательного аргумента - PullRequest
0 голосов
/ 26 июня 2019

Я надеюсь, что это не бьет мертвую лошадь, но я хотел бы узнать мнение о другой возможной стратегии написания ссылочно-прозрачного кода. (Предыдущее обсуждение прозрачности ссылок было на Использование Замыкания вместо Глобальной Переменной ). Опять же, цель состоит в том, чтобы исключить большинство глобальных переменных, но сохранить их удобство, не вставляя подверженные ошибкам ссылки или потенциально нефункциональное поведение (т. Е. Непрозрачность ссылок, побочные эффекты и неповторяющиеся оценки) в код.

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

Что меня интересует, так это то, склонно ли большое количество динамических переменных к программированию. Это не кажется мне особенно подверженным ошибкам, так как любая локальная привязка ранее связанной переменной не должна влиять на исходную привязку после ее освобождения:

(defun main ()
  (let ((x 0))
    (declare (special x))
    (fum)))

(defun fum ()
  (let ((x 1))  ;inadvertant? use of x
    (setf x 2))
  (foo))

(defun foo ()
  (declare (special x))
  (bar x))

(defun bar (arg)  ;final consumer of x
  arg)

(main) => 0

Есть ли проблемы с этим stragegy?

1 Ответ

5 голосов
/ 26 июня 2019

Теперь ваши функции ссылаются на переменную, которая не гарантируется для определения.Попытка выполнить (foo) в ответе приведет к ошибке несвязанной переменной.Мало того, что есть ссылочная непрозрачность, но теперь ошибка выброса ссылочного контекста !

Здесь вы видите глобально связанные подпрограммы, которые могут быть выполнены только в локальном контексте, где (declare (special x)) былнамекал.Вы также можете поместить эти функции в labels, чтобы они случайно не использовались, хотя в этот момент вы выбираете между закрытием переменных в функциях или закрытием функций в функции:

(defun main ()
  (labels ((fum ()
             (let ((x 1));Inadvertent use of x? 
               (setf x 2))
             (foo))
           (foo ()
             (declare (special x))
             (bar x))
           (bar (arg) arg)) ;Final consumer of x.
    (let ((x 0))
      (declare (special x))
      (fum))))

Ух ты, какой-то уродливый код!

После свертки мы можем сделать x лексическим!Теперь мы можем достичь Святого Грааля, ссылочной прозрачности!

Convolute

(defun main ()
  (let ((x 0))
    (labels ((fum ()
               (let ((x 1))
                 (setf x 2))
               (foo))
             (foo () (bar x))
             (bar (arg) arg));Final consumer of x.
      (fum))))

Этот код намного приятнее и шустрее.По сути это ваш код для другого вопроса , но привязки функций локализованы.Это по крайней мере лучше, чем использование взрывного глобального именования.Внутренняя пусть ничего не делает, как и прежде.Хотя теперь он менее запутанный.

CL-USER> (main) ;=> 0

Ваш тестовый пример одинаков (main) ;=> 0 в обоих.Принцип заключается в том, чтобы просто лексически инкапсулировать ваши переменные вместо динамических объявлений special.Теперь мы можем еще больше сократить код, просто передав функциональность в одну переменную среды, , как предложено .

(defun convoluted-zero ()
  (labels ((fum (x)
             (let ((x 1))
               (setf x 2))
             (foo x))
           (foo (x) (bar x))
           (bar (arg) arg)).
    (fum 0)))
CL-USER> (let ((x (convoluted-zero)))
             (list x (convoluted-zero)))
;=> 0

QED ваш кодсо специальными переменными нарушает абстракцию.

Если вы действительно хотите пройти по кроличьей норе, вы можете прочитать раздел главы 6 Дуга Хойта Let Over Lambda о пандорических макросах, где вы можете сделать что-то вроде этого:

(use-package :let-over-lambda)
(let ((c 0))
  (setf (symbol-function 'ludicrous+)
        (plambda () (c) (incf c)))
  (setf (symbol-function 'ludicrous-)
        (plambda () (c)(decf c))))

Затем вы можете использовать pandoric-get, чтобы получить c, не увеличивая его или не определяя любую функцию доступа в этом контексте, что является абсолютным помешательством.С пакетами lisp вы можете избежать локальной глобальной переменной.Например, я могу увидеть приложение для этого в elisp, в котором нет встроенных пакетов.

...