Использование struct для передачи параметров по ссылке

Этот вопрос по теме передачи по ссылке в M (связанный с ним вопрос здесь простой вопрос о передаче данных между функциями)

Пока я пытался найти способ передачи данных по ссылке без использования Unevaluted[] или HoldFirst[], я случайно наткнулся на этот метод, и, похоже, он действительно хорошо работает для меня, хотя я не понимаю, как он работает и какие есть скрытые риски при его использовании. Поэтому я хотел бы показать его здесь экспертам и спросить, считают ли они его безопасным для использования (у меня очень большое демо и мне нужно было упаковать параметры в несколько различных структур, чтобы помочь управлять ими, и вот так я нашел этот метод, пока пробовал).

Вот сам метод: Сначала мы знаем, что нельзя написать следующее:

Remove[p]
foo[p_] := Module[{u},
   u = Table[99, {10}];
   p = u
   ];

p = 0;
foo[p];

Один из способов обновить 'p' в вышеприведенном примере - изменить вызов на become

foo[Unevaluated@p];

Или определив foo[] с HoldFirst.

Но вот способ, который я нашел, который делает передачу по ссылке, не требуя ни того, ни другого:

Я помещаю все параметры в struct (я делаю это в любом случае сейчас), и передаю struct, а затем можно обновить поля struct внутри foo[], и обновления будут отражены в обратном пути от вызова функции:

Remove[parms]
foo[parms_] := Module[{u},
   u = Table[99, {10}];
   parms["p"] = u
   ];

parms["p"] = 0;
foo[parms];

Теперь, parms["p"] содержал новый список {99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}

Таким образом, parms был перезаписан/обновлен внутри foo[] без того, чтобы я сказал M передать parms по ссылке!

Я попробовал это в своей программе, и я не вижу никаких странных побочных эффектов. CDF обновлялся нормально, без ошибок. Я не знаю, как это работает, может быть M рассматривает все поля внутри parms в этом примере как глобальные?

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

Но мой вопрос: видите ли вы серьезные проблемы с этим методом? Как он работает внутри? Я имею в виду, как M обрабатывает эту передачу без того, чтобы я делал HoldFirst или Unevaluated? Я знаю, что теперь я потерял возможность делать проверку параметров, как раньше, но я не могу иметь все, что хочу. Как я уже говорил, M нужен настоящий встроенный struct, как часть языка и интегрированный в него. Но об этом мы поговорим в другой раз.

Кстати, лучшая эмуляция структур, которую я видел до сих пор, это эмуляция Леонида Шифрина, размещенная в конце этой темы здесь, но, к сожалению, я не смог использовать ее в своей демонстрации, так как она использует символы, недопустимые в демонстрационном CDF.

thanks

Update: Кстати, ниже я думаю, как M обрабатывает это. Это просто мое предположение: Я думаю, что param - это своего рода таблица поиска, а ее поля используются в качестве индекса в ней (может быть хэш-таблица?) Тогда param["p1"] будет содержать в себе адрес (не значение) места в куче, где живет фактическое значение param["p1"].

Что-то вроде этого:

enter image description here

Так, при передаче param, затем внутри функции foo[], при наборе param["p1"]=u это приведет к освобождению текущей памяти, на которую указывает "p1", а затем из кучи будет выделена новая память, в которую будет скопировано значение u.

И вот, возвращаясь обратно, мы видим, что содержимое param["p1"] изменилось. Но на самом деле изменилось содержимое памяти, на которую указывает адрес, который представляет param["p1"]. (есть имя, которое "p1", и поле адреса, которое указывает на содержимое, которое представляет имя "p1")

Но тогда это означает, что сам адрес, который "p1" представляет, изменился, но само имя поиска (которое есть "p1") не изменилось.

Значит, поскольку само имя не изменилось, не было необходимости использовать HoldFirst, даже если данные, на которые указывает то, что представляет это имя, были изменены?

Обновление:

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

foo[param_] := Module[{},
   param["u"][[3]] = 99 (*trying to update PART of u *)
   ];

param["u"] = Table[0, {5}];
foo[param];

Приведенное выше дает ошибку

Set::setps: "param[u] in the part assignment is not a symbol"

Но обходной путь прост: сделайте локальную копию всего поля, часть которого вы хотите обновить, затем обновите (часть) локальной копии, затем запишите копию обратно в поле, вот так

foo[param_] := Module[{u = param["u"]}, (* copy the whole field *)
   u[[3]] = 99;  (*update local copy *)
   param["u"] = u (*now update the field, ok *)
   ];

param["u"] = Table[0, {5}];
foo[param];

Ну... Было бы лучше, если бы можно было обновлять часть поля, тогда не требовалась бы "специальная" обработка. Но, по крайней мере, обходной путь не так уж плох.

Update Для полноты картины я подумал, что упомянул еще один маленький нюанс использования индексированных объектов и обходной путь.

Я написал

param[u] = {1, 2, 3}
param[u][[1 ;; -1]]

Что возвращает {1,2,3}, как и ожидалось.

Затем я обнаружил, что могу использовать param@u вместо param[u], что очень помогло, так как от слишком большого количества сплошных скобок у меня начала болеть голова.

Но когда я набрал

param@u[[1 ;; -1]]

ожидая получить тот же ответ, что и раньше, этого не произошло. возникает ошибка, (я думаю, что знаю почему, проблема старшинства операторов, но сейчас не в этом дело), просто хотел сказать, что обходной путь прост, можно использовать param[u][[1 ; ; -1]] или использовать (param@u)[[1 ;; -1]]

Мне больше нравится (param@u)[[1 ;; -1]], поэтому именно его я использую сейчас для доступа к спискам внутри индексированных объектов.

Нотация param@u настолько близка к стандартной нотации записи, которая param.u, так что теперь я доволен индексированными объектами, и, похоже, они работают действительно хорошо. Только пара незначительных моментов, на которые стоит обратить внимание.

7
задан Community 23 May 2017 в 10:32
поделиться