Переменные ссылки в lisp - PullRequest
24 голосов
/ 09 августа 2009

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

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

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

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

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

РЕДАКТИРОВАТЬ : Несколько человек предложили использовать incf. Я использовал этот пример только для демонстрации проблемы простым способом, на самом деле я не искал переопределения incf. Но все равно спасибо за предложения.

Ответы [ 6 ]

22 голосов
/ 09 августа 2009

С лексической областью у человека нет доступа к переменным, которые не находятся в текущей области. Вы также не можете передавать лексические переменные другим функциям напрямую. 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))))

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

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

(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
9 голосов
/ 09 августа 2009

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

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

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

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

(increase-by-one foo)

, который будет расширен на:

(setf foo (+ foo 1))

до его компиляции.

7 голосов
/ 09 августа 2009

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

(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
1 голос
/ 11 августа 2009

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

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

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

0 голосов
/ 27 декабря 2011

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

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

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

0 голосов
/ 09 августа 2009

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

...