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

Въпросът за създаване на запис като в 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=-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 Rules и обектите, върнати от GraphEdit.   -  person WReach    schedule 09.09.2011


Отговори (2)


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

В публикацията си тук обсъдих възможен начин за изграждане на променливи структури от данни където методите могат да правят допълнителни проверки. Ще копирам съответното парче тук:

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. Каква част от първоначалния въпрос на Nasser не решава използването на 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