Безопасная настройка структуры в Mathematica

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

Проблема со всеми этими методами заключается в том, что, по-видимому, теряется возможность выполнять конкретную дополнительную проверку каждого аргумента, например, когда выполняется 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=-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, это настоящая запись, помогающая управлять и организовывать все переменные, используемые в программе.


person Nasser    schedule 09.09.2011    source источник
comment
В этом вопросе обсуждается моделирование типов структуры/записи: В чем разница между правилами Mathematica и объектами, возвращаемыми GraphEdit.   -  person WReach    schedule 09.09.2011


Ответы (2)


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

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

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]

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

person Leonid Shifrin    schedule 09.09.2011
comment
Леонид, я надеюсь когда-нибудь увидеть код довольно большого приложения Mathematica, в котором собраны некоторые из ваших концепций, чтобы я мог увидеть, как ваши методы влияют на программирование в целом. - person Mr.Wizard; 26.10.2011
comment
@Mr.Wizard Зависит от того, что вы бы назвали довольно большим, но у меня есть несколько таких в разработке. Когда-нибудь я их опубликую. - person Leonid Shifrin; 26.10.2011
comment
Обратите внимание, что структура Association, добавленная в Mathematica 10, существенно решает эту проблему. См. мой ответ. - person Jess Riedel; 01.11.2015
comment
@JessRiedel Я не согласен. Это решает только часть проблемы. И в любом случае, я не вижу смысла в этом комментарии: вы действительно думаете, что я не знал об ассоциациях или о том, как их можно использовать в этом контексте? - person Leonid Shifrin; 01.11.2015
comment
Да, я полагал, что вы не знали о Associations, когда писали и последний раз редактировали этот пост в сентябре 2011 года, потому что Associations не были представлены до Mathematica 10. Какую часть исходного вопроса Насера ​​не решает использование Associations (или DataSet)? - person Jess Riedel; 01.11.2015
comment
@JessRiedel Nasser хочет иметь возможность проверять аргументы на основе шаблонов, как это обычно делается в Mathematica. Мой ответ показывает, как это сделать. С Ассоциациями нет простого способа сделать то же самое. В любом случае, мое общее мнение таково, что Ассоциации решают только часть проблемы. Вы можете использовать их как структуры, но если вы хотите присоединить методы, а не только поля, вы потеряете богатство сопоставления с образцом Mathematica. Я не спорю, что Ассоциации полезны в этом контексте, конечно. - person Leonid Shifrin; 01.11.2015
comment
Хорошо, я обновил свой ответ, чтобы прояснить, как выполняется проверка аргументов с использованием Association. На мой взгляд, это намного проще и безопаснее, чем копаться в Protect, особенно для ограниченного контекста вопроса Насера. Вне этого контекста вам может понравиться возможность определять стандартные типы структур, проверять, относится ли данная структура к этому типу и т. д. Ассоциации не могут сделать это легко, потому что они атомарны, но моя интуиция подсказывает, что накладные расходы и риски использование вашей техники почти никогда не будет стоить того. Я был бы заинтересован в ссылке на проблему, где вы думаете, что это. Ваше здоровье! - person Jess Riedel; 02.11.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] просто остается невыполненной, поскольку аргумент не проходит проверку NumericalQ:

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|>

потому что ассоциации атомарны. См. здесь.

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

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

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


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

person Jess Riedel    schedule 01.11.2015