Подтвердить что ты не робот

Переменные ссылки в lisp

Еще один новичок (общий) LISP вопрос:

В основном в большинстве языков программирования существует среднее значение для функций для получения ссылок на переменные вместо простых значений, т.е. передачи по ссылке вместо передачи по значению. Скажем, для простоты я хочу написать функцию LISP, которая получает переменную и увеличивает значение переменной на единицу:

(defun increase-by-one (var)
  (setf var (+ var 1)))

Теперь очевидно, что проблема заключается в том, что эта функция только увеличивает значение копии переменной в стеке, а не фактическую исходную переменную. Я также пытался добиться эффекта, используя макросы без особого успеха, хотя у меня такое чувство, что использование макросов - правильный путь.

Я все время нахожу эту стену в LISP, и я уверен, что вокруг нее должен быть какой-то способ или, возможно, совсем другой подход к этой проблеме в LISP, о котором я не думал? Как это делается в LISP?

EDIT. Несколько человек предложили использовать incf. Я только использовал этот пример, чтобы продемонстрировать проблему простым способом, я действительно не искал повторного выполнения incf. Но спасибо за предложения в любом случае.

4b9b3361

Ответ 1

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

Думайте функционально!

(let ((a 1))
  (values (lambda (new-value)
            (setf a new-value)) 
          (lambda () a)))

выше возвращает две функции. Можно прочитать переменную, другую можно записать переменную.

Пусть называют первую функцию writer, а второй один reader.

(defun increase-by-one (writer reader)
   (funcall writer (1+ (funcall reader))))

Итак, чтобы сделать то, что вы хотите, код должен: a) находиться в области или b) иметь доступ к функциям, находящимся в области.

Также переменная может быть глобальной.

(defvar *counter* 1)

(defun increase-by-one (symbol)
  (set symbol (1+ (symbol-value symbol))))
  ; note the use of SET to set a symbol value

(increase-by-one '*counter*)

Это работает для глобальных переменных, которые представлены символом. Это не работает для лексических переменных - они не представлены символом.

Существует также макрос INCF который увеличивает "место" (например, переменную).

(incf a)

Но a - переменная в текущей области.

(defun foo (a)
  (incf a))  ; increases the local variable a

Предел можно увидеть здесь:

(defun foo (var)
  (add-one-some-how var))

(let ((a 1))
   (foo something-referencing-a))

Там нет никакого способа передать прямое обращение к a FOO.

Единственный способ - предоставить функцию. Мы также должны переписать FOO, чтобы он вызывал предоставленную функцию.

(defun foo (f)
  (funcall f 1))   ; calls the function with 1

(let ((a 1))
   (foo (lambda (n)
          (setf a (+ a n)))))
   ;; passes a function to foo that can set a

Ответ 2

В то время как Common Lisp поддерживает стиль функционального программирования, это не его общий фокус (схема, хотя и не является чисто функциональной, намного ближе). Common Lisp очень хорошо поддерживает совершенно императивный стиль программирования.

Если вы обнаружите, что вам нужно написать такой код, обычным решением является макрос:

(defmacro increase-by-one (var)
  `(setf ,var (+ ,var 1)))

Это позволяет вам писать код типа:

(increase-by-one foo)

который будет разложен на:

(setf foo (+ foo 1))

перед компиляцией.

Ответ 3

Конечно, в Lisp вы можете сделать свой собственный способ сделать ссылки на переменные, если хотите. Самый простой способ:

(defstruct reference getter setter)

(defmacro ref (place)
  (let ((new-value (gensym)))
    `(make-reference :getter (lambda () ,place)
                     :setter (lambda (,new-value)
                               (setf ,place ,new-value)))))

(defun dereference (reference)
  (funcall (reference-getter reference)))

(defun (setf dereference) (new-value reference)
  (funcall (reference-setter reference) new-value))

И затем вы можете использовать его:

(defun increase-by-one (var-ref)
  (incf (dereference var-ref)))

(defun test-inc-by-one (n)
  (let ((m n))
    (increase-by-one (ref m))
    (values m n)))

(test-inc-by-one 10) => 11, 10

Ответ 4

Макросы, вероятно, вы хотите, потому что они не оценивают свои аргументы, поэтому, если вы передаете имя переменной, вы получите имя переменной, а не ее значение.

INCF делает именно то, что вы хотите, поэтому, если вы используете "defmacro incf", вы найдете целую кучу определений для этого, некоторые из которых даже близки к правильности.: -)

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

Ответ 5

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

Ответ 6

В качестве новичка я пришел сюда, чтобы понять, как делать то, что должно быть тривиальной процедурой на любом языке. Большинство вышеперечисленных решений не работают должным образом, возможно, были более сложными, чем то, что было необходимо, или различными реализациями. Вот простое решение для SBCL:

(defmacro inc-by-num (var num)
           (set var (+ (eval var) num)))

По-видимому, вы не можете использовать setf b/c, он ограничивает область действия, тогда как set - нет. Также вам может понадобиться использовать eval до var, если вы получите ошибку "аргумент X не является числом".