Безопасная настройка структуры в Mathematica - PullRequest
5 голосов
/ 09 сентября 2011

Вопрос о создании записи, как в Mathematica, обсуждался в нескольких местах, например, Тип данных Struct в Mathematica? .

Проблема всех этих методов в том, что, похоже, человек теряет способность специфическая дополнительная проверка для каждого аргумента, например, когда x_?NumericQ.

Мой вопрос таков: есть ли в Mathematica способ сделать запись или структуру, и при этом иметь возможность использовать проверку, как указано выше, для отдельных элементов?

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

Сначала я покажу три метода, о которых знаю.

Метод 1

foo[p_]:=Module[{},
    Plot[Sin[x],{x,from/.p,to/.p}]
]
p={from->-Pi,to->Pi};
foo[p]

Преимущество: безопасно, как будто я поменяю символ «с» на что-то другое, оно все равно будет работать. Как следующий пример.

foo[p_]:=Module[{},
    Plot[Sin[x],{x,from/.p,to/.p}]
]
p={from->-Pi,to->Pi};
from=-1; (* By accident the symbol from was set somewhere. It will work*)
foo[p]

Метод 2

Clear[p,foo];
foo[p_]:=Module[{},
    Print[p];
    Plot[Sin[x],{x,p["from"],p["to"]}]
]
p["from"] = -Pi;
p["to"]   = Pi;

foo[p]

Преимущество: также безопасно, строки неизменны. Не нужно беспокоиться об изменении значения «из». Но наличие строк везде не слишком читабельно?

Метод 3

Clear[p,to,from];
foo[p_]:=Module[{},
    Plot[Sin[x],{x,p[from],p[to]}]
]
p[from] = -Pi;
p[to]   = Pi;

foo[p]

Недостаток: если какой-либо из символов 'from' или 'to' где-то будет перезаписан, возникнет проблема, как в

from=-4; (*accidentally the symbol from is assigned a value*)
foo[p]   

Так. Я думаю, что метод (1) является наиболее безопасным. Но теперь я теряю способность делать это:

foo[from_?NumericQ, to_?NumericQ] := Module[{},
    Plot[Sin[x], {x, from, to}]
]
from = -Pi; to = Pi;
foo[from, to]

Итак, я надеюсь, что у меня появится идея объединить создание «записи», но, в то же время, все еще можно будет использовать проверку параметров для отдельных элементов в записи? Или этот вопрос плохо сформулирован для функционального / основанного на правилах стиля программирования Mathematica?

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

Ответы [ 2 ]

10 голосов
/ 09 сентября 2011

Во-первых, я хотел бы отметить, что все методы, которые вы перечислили, являются ошибочными и опасными.Основная причина, по которой они мне не нравятся, заключается в том, что они вводят неявные зависимости от глобальных переменных (причины, по которым это плохо, обсуждаются, например, здесь ), а также могут испортить область видимости.Другая их проблема заключается в том, что эти подходы выглядят так, что они не будут хорошо масштабироваться для многих экземпляров ваших структур, существующих одновременно.Второй метод, который вы перечислили, кажется самым безопасным, но он также имеет свои проблемы (строки как имена полей, нет способа проверить тип такой структуры, также используемые там символы могут случайно иметь значение).

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

Unprotect[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
ClearAll[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
Module[{first, second},
   first[_] := {};
   second[_] := {};
   pair /: new[pair[]] := pair[Unique[]];
   pair /: new[pair[],fst_?NumericQ,sec_?NumericQ]:= 
      With[{p=new[pair[]]}, 
          p.setFirst[fst];
          p.setSecond[sec];
          p];
   pair /: pair[tag_].delete[] := (first[tag] =.; second[tag] =.);
   pair /: pair[tag_].setFirst[value_?NumericQ] := first[tag] = value;
   pair /: pair[tag_].getFirst[] := first[tag];
   pair /: pair[tag_].setSecond[value_?NumericQ] := second[tag] = value;
   pair /: pair[tag_].getSecond[] := second[tag];       
];
Protect[pair, setFirst, getFirst, setSecond, getSecond, new, delete]; 

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

Ваш пример теперь будет выглядеть так:

foo[from_?NumericQ, to_?NumericQ] :=
   Module[{}, Plot[Sin[x], {x, from, to}]];
foo[p_pair] := foo[p.getFirst[], p.getSecond[]]
pp = new[pair[], -Pi, Pi];
foo[pp]

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

0 голосов
/ 01 ноября 2015

Mathematica 10 представила Association, который обладает многими наиболее важными свойствами struct (и имеет синтаксис, аналогичный правилам замены, с которыми вы экспериментировали).

plotLimits = <| "lowerLimit" -> -Pi, "upperLimit" -> Pi |>; 
(*this is the syntax for an Association[]*)

foo[p_]:=Module[{},
 Plot[Sin[x],{x,p["lowerLimit"],p["upperLimit"]}]
];
(* assoc["key"] is one of many equivalent ways to specify the data *)

Мы также можем легко реализовать проверку аргументов

fooWithChecks[p_?(NumericQ[#["lowerLimit"]] && NumericQ[#["upperLimit"]] &)] := Module[{}, 
 Plot[Sin[x], {x, p["lowerLimit"], p["upperLimit"]}]
];

В этом случае foo[plotLimits] и fooWithChecks[plotLimits] дают один и тот же график, поскольку plotLimits имеет хорошие числовые значения.Но если мы определим

badPlotLimits = <|"lowerLimit" -> bad, "upperLimit" -> Pi|>;

, то оценка foo[badPlotLimits] даст ошибку

Plot::plln: Limiting value bad in {x,<|lowerLimit->bad,upperLimit->2 \[Pi]|>[lowerLimit],<|lowerLimit->bad,upperLimit->2 \[Pi]|>[upperLimit]} is not a machine-sized real number. >>
Plot[Sin[x], {x, <|"lowerLimit" -> bad, "upperLimit" -> 2 \[Pi]|>["lowerLimit"], <|"lowerLimit" -> bad, "upperLimit" -> 2 \[Pi]|>["upperLimit"]}]

, но оценка fooWithChecks[badPlotLimits] просто останется без оценки, поскольку аргумент не проходит проверку NumericQ:

fooWithChecks[<|"lowerLimit" -> bad, "upperLimit" -> 2 \[Pi]|>]

Мне не понятно, почему вы спрашиваете о форме foo[from_?NumericQ, to_?NumericQ], а не foo[p_?(someCheckFunction)].Ключевым преимуществом наличия структуры в первую очередь является то, что вы можете реорганизовать, как структура хранится в памяти, скажем, меняя порядок «lowerLimit» и «upperLimit», не переписывая ни одну из функций, которые ее используют (так как они называют это p["lowerLimit"], а не p[[1]]).Эта способность нарушается, если вы определяете foo таким образом, что при вызове foo аргументы выводятся по порядку.(Другими словами, вы не позволяете foo узнать о структуре.) Конечно, вы все еще можете сделать это, возможно, потому что вы хотите использовать foo и для неструктур:

foo[from_?NumericQ, to_?NumericQ] :=
 Module[{}, Plot[Sin[x], {x, from, to}]];
foo[p] := foo[p["lowerLimit"], p["upperLimit"]];

Если вы хотите быть очень осторожным, вы можете использовать это:

foo[p_?(SubsetQ[Keys[#],{"lowerLimit", "upperLimit"}]&)] :=
 foo[p["lowerLimit"], p["upperLimit"]];

К сожалению, вы не можете дать имена определенным Association шаблонам (которые будут Association аналог этот метод для списков) с использованием чего-то подобного

plotLimitType=<|"lowerLimit"->_NumericQ, "upperLimit"->_NumericQ|>

, потому что ассоциации являются атомарными (ish).См. здесь .

Кстати, обратите внимание, что такие ключи, как "lowerLimit" не обязательно должны быть в кавычках.Использование этого стиля

plotLimits = <|lowerLimit -> -Pi, upperLimit -> Pi|>;

также работает.


Для получения дополнительной информации см.

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