Использование структуры для передачи параметров по ссылке - PullRequest
7 голосов
/ 15 декабря 2011

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

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

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

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

p = 0;
foo[p];

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

foo[Unevaluated@p];

Или путем определения foo[] с помощью HoldFirst.

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

Я помещаю все параметры в структуру (я делаю это в любом случае сейчас), и передаю структуру, а затем можно обновить поля структуры внутри 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}

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

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

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

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

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

спасибо

Обновление: Кстати, это ниже, как я думаю, какМ справляется с этим.Это только мое предположение: я думаю, 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];

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

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

Я написал

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, так что теперь я доволен индексированными объектами, и кажется, что она работает очень хорошо. Просто пара мелких вещей, на которые стоит обратить внимание.

1 Ответ

5 голосов
/ 15 декабря 2011

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

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

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

Remove[parms, foo]

foo[thing_] :=
  Module[{u},
    u = Table[99, {10}];
    thing["p"] = u
  ];

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

parms["p"]

DownValues[parms]
{HoldPattern[parms["p"]] :> {99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}

Это показывает, как данные хранятся в терминах структур высокого уровня Mathematica .

parms является глобальным символом. Как вы уже заметили, его можно безопасно «пропустить» без каких-либо событий, потому что это просто символ без OwnValues и при его оценке ничего не срабатывает. Это то же самое, что написать / оценить foo сам по себе.

Замена в foo[thing_] := ... аналогична With. Если мы напишем:

With[{thing = parms}, thing[x]]

thing в thing[x] заменяется на parms до thing[x] оценки. Аналогично, в приведенном выше коде мы получаем parms["p"] = u до thing["p"] или Set. Тогда атрибут HoldFirst Set вступает во владение, и вы получаете то, что хотите.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...