Передача данных по ссылке

Не секрет, что друзья не растут в огороде данные в функции нередко передаются по значению. В C++ / C# / VB во всех его реинкарнация - там везде можно передавать данные по ссылке. А что надо сделать в лиспе, чтоб такое же было?

Думаю, что стоит затронуть вопрос "а на фига?"

Попробую довести ситуацию до абсурда: в результате вычисления некоторой функции (или чтения какого-то файла) получили список весом этак под 100 Mb. При передаче данных "по значению" все эти 100 Mb надо будет скопировать, обработать и заменить. Помимо того, что в памяти образуется "дырка", памяти может просто не хватить. Касается это, конечно, пока что только 32-разрядных систем, но тем не менее.

Кстати, настолько привычного для C# сборщика мусора в LISP не предусмотрено, кажется: стандартная функция (gc) срабатывает когда ей захочется, а не когда ее вызвали :(

Рассмотрим элементарный пример:

1
2
3
4
5
(defun fun1 (/ lst1)
  (setq lst1 '(1 2 3 4))
  (princ (strcat "\nres         : " (vl-prin1-to-string lst1)))
  (princ)
  ) ;_ end of defun

Результат выполнения предсказуем:

1
2
_$ (fun1)
(1 2 3 4)

Теперь введем дополнительную функцию fun2 и сформируем для нее вызов таким образом, чтобы данные передавать именно по ссылке. А заодно и на результаты посмотрим:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(defun fun1 (/ lst1)
  (setq lst1 '(1 2 3 4))
  (princ (strcat "\nstart       : " (vl-prin1-to-string lst1)))
  (fun2 'lst1)
  (princ (strcat "\nres         : " (vl-prin1-to-string lst1)))
  (princ)
  ) ;_ end of defun

(defun fun2 (lst2)
  (princ (strcat "\nlst2        : " (vl-prin1-to-string lst2)))
  (princ (strcat "\n(eval lst2) : " (vl-prin1-to-string (eval lst2))))
  (set lst2
       (mapcar
         (function 1+)
         (eval lst2)
         ) ;_ end of mapcar
       ) ;_ end of setq
  ) ;_ end of defun

Тут уже результат становится иным, причем принципиально:

1
2
3
4
5
6
_$ (fun1)

start       : (1 2 3 4)
lst2        : LST1
(eval lst2) : (1 2 3 4)
res         : (2 3 4 5)

Попробую разобраться по шагам без картинок и видео:

Код Пояснение
1
2
(setq lst1 '(1 2 3 4))
  (princ (strcat "\nstart       : " (vl-prin1-to-string lst1)))
Ну, тут все просто: назначаем параметру начальные значения. И за компанию вывод в ком.строку
1
(fun2 'lst1)
Собственно вызов fun2 с передачей параметра по ссылке - обратите внимание на апостроф (аналог знаменитой quote)
1
  (princ (strcat "\nlst2        : " (vl-prin1-to-string lst2)))
Переходим в fun2, и сразу выводим в ком.строку то значение, которое передано. Обратите внимание на результат: "lst2 : LST1". Т.е. передается не значение, а символ переменной, т.е. ссылка (если, конечно, я правильно понимаю ситуацию ;))
1
  (princ (strcat "\n(eval lst2) : " (vl-prin1-to-string (eval lst2))))
А теперь вычислим значение переданного символа - eval в руки и вперед. Результат не замедлил себя ждать: "(eval lst2) : (1 2 3 4)"
1
2
3
4
5
6
(set lst2
       (mapcar
         (function 1+)
         (eval lst2)
         ) ;_ end of mapcar
       )
Теперь мы в lst2 загоняем измененный список. Опять же, не все привычно: вместо setq используется set
1
  (princ (strcat "\nres         : " (vl-prin1-to-string lst1)))
Возвращаемся в fun1 и выводим то, что у нас теперь хранится в lst1: "res : (2 3 4 5)"

P.S. Пост написан прежде всего для себя, но буду безумно рад критике и разъяснениям :)

Размещено в Код LISP · Метки: , ,



Комментарии

Есть 6 коммент. к “Передача данных по ссылке”
  1. Дима_ пишет:

    Алексей, Вы меня поражаете, а для чего по Вашему списки немутабельны?? В лиспе, если использовать сишную терминологию, ВСЕ передается ТОЛЬКО по ссылке. В вашем примере с помощью set вы переопределяете ИМЯ хранящиеся в lst2 (то есть "lst1") вот и все. Что set, что setq ТОЛЬКО переопределяет имя, предоставляя предыдущее значение сборщику - проверяется елементарно - с помошью любого мутабельного объекта (vla или ename)
    создаем круг
    (setq a (entlast))
    (setq b a x b c x xx c) - теперь двигаем круг сколько хотим
    (cdr (assoc 10 (entget XXX))) на месте XXX ставте любое из созданных имен - результат будет ВСЕГДА одинаков (переместите и они все одинаково изменятся) - т.е. это все естественно "ссылки" на наш круг а не его копии.
    (setq AAA (cdr (assoc 10 (entget a)))) - вот так будет занесено (сохранено) значение, которое естественно не изменится как-бы мы дальше не крутили кругом. В автолиспе нет "переменных" - есть только имена которые указывают на конкретные значения (ячейки памяти), а когда на "свои" (самосозданные) значения ни указывает ни одна ссылка (то есть оно уже точно никому не нужно) - его на очередном проходе "освобождает" сборщик мусора.

  2. Кулик Алексей aka kpblc пишет:

    Хорошо, что в таком случае вообще делает set? По-моему, если использовать именно терминологию С++, то set - передача по ссылке.
    И как тогда объяснить такое поведение кода

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    (defun fun1 (/ lst1)
      (setq lst1 '(1 2 3))
      (princ (strcat "\nfun1 lst1 : " (vl-prin1-to-string lst1)))
      (fun2 lst1)
      (princ (strcat "\nfun1 lst1 : " (vl-prin1-to-string lst1)))
      (princ)
      ) ;_ end of defun

    (defun fun2 (lst2)
      (princ (strcat "\nfun2 lst1 : " (vl-prin1-to-string lst1)))
      (setq lst2 (mapcar '1+ lst2))
      (princ (strcat "\nfun2 lst2 : " (vl-prin1-to-string lst2)))
      ) ;_ end of defun
    1
    2
    3
    4
    5
    6
    7
    _$ (fun1)

    fun1 lst1 : (1 2 3)
    fun2 lst1 : (1 2 3)
    fun2 lst2 : (2 3 4)
    fun1 lst1 : (1 2 3)
    _$

    У меня другого объяснения, кроме как передача по значению, нет.

  3. Дима_ пишет:

    SET делает ровно то-же что и SETQ, только принимает аргументы имени в другом формате - setq служит для связывания "жесткого" програмного имени со значением, а set позоляет использовать имена которые могут "придумываются" уже во время выполнения программы (то есть они и их количество может быть различными при различных исходных данных - имена построенны на основе самих данных) - на самом деле это можно сделать и при помощи setq и eval - но это т.н. "синтетический сахар".
    Как это работает:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    (defun fun1 (/ lst1) ;локальное имя
       (setq lst1 '(1 2 3)); заносим в lst1 ссылку на начало списка 1 2 3
       (princ (strcat "\nfun1 lst1 : " (vl-prin1-to-string lst1))) ; выводим то что по ссылке в lst1 (соответственно 1 2 3)
       (fun2 lst1) ;вызываем функцию fun2 передавая в качестве аргумента ссылку на 1 2 3
       (princ (strcat "\nfun1 lst1 : " (vl-prin1-to-string lst1))); выводим то что по ссылке в lst1 (соответственно 1 2 3)
       (princ)
       ) ;_ end of defun

    (defun fun2 (lst2) ;ассоциируем имя lst2 с ссылкой на 1 2 3
       (princ (strcat "\nfun2 lst1 : " (vl-prin1-to-string lst1))); выводим то что ассоциированно с lst1 (см. строку 3 - 1 2 3)
       (setq lst2 (mapcar '1+ lst2)); ассоциируем имя lst2 на ссылку на вновь созданный список 2 3 4
       (princ (strcat "\nfun2 lst2 : " (vl-prin1-to-string lst2))); выводим его
       ) ;_ end of defun; возвращаемся в fun1
  4. Привалов Дмитрий пишет:

    Хорошая статья, мне как раз нужно было передать ссылку на переменную в функцию.

    Чтобы лучше разобраться.

    ;1й пример
    (defun fun1(val)
    (print val)
    )

    (setq A2 3)
    (fun1 A2) ; 3 - выведет значение
    (fun1 'A2) ; A2 - выводит имя ссылки

    ;2й пример
    (setq A2 3)
    (set 'A2 4) ; SET присваивает ссылке с именем A2 значение 4.
    (print A2) ; 4 - выведет значение

    ;3й пример
    (set 'A2 4) ; SET создает не объявленную ранее переменную A2 и присваивает ссылке с именем A2 значение 4.
    (print A2) ; 4 - выведет значение

    комментарий: возможно ДИМА_ ошибся с set в том, что она может как создавать переменную с новым именем, так и менять существующую. Но возможно в чем то и прав, т.к. при сравнении значений есть нюансы.

    ;4й пример
    (defun fun1(val)
    (print (= val 3))
    (print (< val 4))
    )

    (setq A2 3)
    (fun1 A2) ; Вернет Т и Т
    (fun1 'A2) ; Вернет nil и ошибка: неверный тип агрумента для сравнения: A2 4

    ; примечание: аГРумента мне выдал AutoCAD 2010 )))
    ; в статье освещено как присвоить переменной, переданной по ссылке значение внутри функции. мне в функции нужно было работать со значением внешней переменной. Собственно ради этого и разбирался.

    ; 5й пример
    (defun fun1(val)
    (print (= !val 3))
    (print (", "<" научился (< !val 4) и мне достаточно для задачи.
    а вот с равенством есть над чем подумать. Попробовал (print (equal !val 3)) не помогло!

  5. Привалов Дмитрий пишет:

    сообщение обрезалось....
    ; 5й пример
    (defun fun1(val)
    (print (= !val 3))
    (print (< !val 4))
    )

    (fun1 A2) ; вернет nil и T
    (fun1 'A2) ; вернет nil и T

    Примечание: перепроверил то, что не влезло.
    с виду (print (< !val 4)) сработало. А (print (= !val 3)) нет. О чем пытался написать ранее.
    но протестировав варианты пришел к выводу, что !val не дает доступ к значению переменной.

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

    Также можно записать (set (quote A2) 4) как аналог (set 'A2 4) .

  6. Привалов Дмитрий пишет:

    Ну все, кажется разобрался

    (defun fun1(val)
    (print (vl-symbol-name val))
    (print (vl-symbol-value val))
    )

    (setq A2 3)
    (fun1 'A2)

Поделитесь своим мнением


Я не робот.