Передача данных по ссылке
Не секрет, что друзья не растут в огороде данные в функции нередко передаются по значению. В 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) |
Попробую разобраться по шагам без картинок и видео:
Код | Пояснение | ||
| Ну, тут все просто: назначаем параметру начальные значения. И за компанию вывод в ком.строку | ||
| Собственно вызов fun2 с передачей параметра по ссылке - обратите внимание на апостроф (аналог знаменитой quote) | ||
| Переходим в fun2, и сразу выводим в ком.строку то значение, которое передано. Обратите внимание на результат: "lst2 : LST1". Т.е. передается не значение, а символ переменной, т.е. ссылка (если, конечно, я правильно понимаю ситуацию ;)) | ||
| А теперь вычислим значение переданного символа - eval в руки и вперед. Результат не замедлил себя ждать: "(eval lst2) : (1 2 3 4)" | ||
| Теперь мы в lst2 загоняем измененный список. Опять же, не все привычно: вместо setq используется set | ||
| Возвращаемся в fun1 и выводим то, что у нас теперь хранится в lst1: "res : (2 3 4 5)" |
P.S. Пост написан прежде всего для себя, но буду безумно рад критике и разъяснениям
Алексей, Вы меня поражаете, а для чего по Вашему списки немутабельны?? В лиспе, если использовать сишную терминологию, ВСЕ передается ТОЛЬКО по ссылке. В вашем примере с помощью 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)))) - вот так будет занесено (сохранено) значение, которое естественно не изменится как-бы мы дальше не крутили кругом. В автолиспе нет "переменных" - есть только имена которые указывают на конкретные значения (ячейки памяти), а когда на "свои" (самосозданные) значения ни указывает ни одна ссылка (то есть оно уже точно никому не нужно) - его на очередном проходе "освобождает" сборщик мусора.
Хорошо, что в таком случае вообще делает set? По-моему, если использовать именно терминологию С++, то set - передача по ссылке.
И как тогда объяснить такое поведение кода
2
3
4
5
6
7
8
9
10
11
12
13
(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
2
3
4
5
6
7
fun1 lst1 : (1 2 3)
fun2 lst1 : (1 2 3)
fun2 lst2 : (2 3 4)
fun1 lst1 : (1 2 3)
_$
У меня другого объяснения, кроме как передача по значению, нет.
SET делает ровно то-же что и SETQ, только принимает аргументы имени в другом формате - setq служит для связывания "жесткого" програмного имени со значением, а set позоляет использовать имена которые могут "придумываются" уже во время выполнения программы (то есть они и их количество может быть различными при различных исходных данных - имена построенны на основе самих данных) - на самом деле это можно сделать и при помощи setq и eval - но это т.н. "синтетический сахар".
Как это работает:
2
3
4
5
6
7
8
9
10
11
12
13
(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
Хорошая статья, мне как раз нужно было передать ссылку на переменную в функцию.
Чтобы лучше разобраться.
;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й пример
(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) .
Ну все, кажется разобрался
(defun fun1(val)
(print (vl-symbol-name val))
(print (vl-symbol-value val))
)
(setq A2 3)
(fun1 'A2)