Подтвердить что ты не робот

Что означает "upvalue" в Mathematica и когда их использовать?

Для меня g /: f[g[x_]] := h[x] является просто многословным эквивалентом f[g[x_]] := h[x]. Можете ли вы привести пример, который вы должны использовать /:?

4b9b3361

Ответ 1

Собственно, g /: f[g[x_]] := h[x] не эквивалентен f[g[x_]] := h[x]. Последнее связывает определение с f, а TagSet (/:) и UpSet (^= и отложенная версия, ^:=) связывают определение с помощью g, Это ключевое различие и может быть проиллюстрировано простым примером. Скажем, вы хотите иметь набор переменных, которые подчиняются по модулю 5, т.е. 6 + 7 mod 5 = 3. Итак, мы хотим, чтобы что-либо с Head mod вел себя корректно. Первоначально мы думаем, что

a_mod + b_mod := [email protected][a + b, 5]

будет работать. Но он генерирует ошибку

SetDelayed::write : Tag Plus in a_mod + b_mod is Protected.

Мы могли бы удалить Unprotect Plus, и тогда наше определение будет работать, но это может вызвать проблемы с другими определениями и, поскольку Plus накапливает больше определений, оно замедляется. В качестве альтернативы мы можем связать свойство сложения с самим объектом mod через TagSet

mod /: a_mod + b_mod := mod @ Mod[a + b, 5]

или UpSetDelayed

a_mod + b_mod ^:= mod @ Mod[a + b, 5]

Настройка upvalue несколько верна с концептуальной точки зрения, так как mod является тем, у кого есть другое свойство.

Есть несколько вопросов, о которых нужно знать. Во-первых, механизм upvalue может только сканировать один уровень в глубину, т.е. Plus[a_mod, b_mod] отлично, но Exp[Plus[a_mod, b_mod]] выдаст ошибку. Это может потребовать от вас творческого подхода к промежуточному типу. Во-вторых, с точки зрения кодирования UpSetDelayed легче писать, но иногда возникает некоторая двусмысленность, относительно которой Head является upvalue, связанным с. TagSet обрабатывает это, явно называя соответствующий Head, и, в общем, это то, что я предпочитаю более UpSet.

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

a_ \[CircleTimes] b_ := KroneckerProduct[a,b]
a_ \[CircleTimes] b_ \[CircleTimes] c__ := a \[CircleTimes] ( b \[CircleTimes] c )

и

a_ \[CirclePlus] b__ := BlockDiagonal[{a,b}]

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

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

Пример: простой и полезный пример обозначает Symbol как Real:

makeReal[a__Symbol] := (
     # /: Element[#, Reals] := True; 
     # /: Im[#] := 0; 
     # /: Re[#] := #;
     # /: Abs[#] := Sign[#] #;
     # /: Arg[#] := Piecewise[{{0, Sign[#] >= 0}, {Pi, Sign[#] < 0}}]
   ) & /@ List[a]

Обратите внимание, что использование TagSet в качестве Element[ a, Reals ] ^:= True было бы неоднозначным. Каким будет правило для a или Reals? Кроме того, если бы мы хотели получить положительное вещественное число, мы могли бы установить Arg[#]:=0, который позволяет Simplify вести себя так, как ожидалось, например. Simplify[Sqrt[a^2]] == a.

Ответ 2

В дополнение к отличному ответу @rcollyer, я хотел бы подчеркнуть еще несколько важных вещей о UpValues.

Мягкое/локальное переопределение системы и других функций

Один очень важный аспект заключается в том, что они позволяют "мягко" перегрузить некоторые системные функции только на определенные символы. Важность этого указала @rcollyer, но не может быть подчеркнута достаточно - это делает эффект вашего кода локальным и резко снижает вероятность того, что ваш код может глобально взаимодействовать и воздействовать на какую-либо другую часть системы или другой фрагмент пользовательский код, в отличие от системных символов Unprotect и добавить к ним DownValues.

Помимо безопасного и локального, такие переопределения также могут быть довольно общими, если использовать конструкции типа yourSymbol/:f_[_yourSymbol,rest___]:=.... Они должны использоваться с осторожностью, но иногда могут давать очень сжатые и простые решения. Здесь - один из примеров, когда один код может быть использован для "перегрузки" нескольких системных функций сразу, что дает им дополнительную нетривиальную функциональность.

Порядок оценки

Следующий момент - оценка. Общее утверждение, с которым вы можете столкнуться, - "UpValues применяется до DownValues". Это необходимо уточнить: для f[g[args]] это означает, что UpValues для g применяется до DownValues для f, при условии, что процесс оценки уже прошел весь путь "вниз" до самых внутренних частей, а затем пошел резервное копирование". В частности, это не означает, что UpValues для g будет применяться до DownValues для g - если g[args] может оценивать внутри f, потому что g имеет соответствующий DownValues, он будет ( если f не имеет одного из атрибутов Hold), а наличие UpValues не будет препятствовать этому, потому что (для стандартной оценки) оценка g[args] происходит до оценки f[result-of-evaluation-of g[args]]. Например, здесь:

In[58]:= 
ClearAll[f, g];
f[x_] := x^2;
g /: f[g[x_]] := Sin[g[x]];
g[x_] := Cos[x];

In[62]:= f[g[y]]
Out[62]= Cos[y]^2

UpValues для g не имел возможности применить, поскольку g[y] преобразуется в Cos[y] на предыдущем этапе оценки. Ситуация будет отличаться для нестандартной оценки - либо если мы дадим атрибуты f HoldAll или HoldFirst, либо если мы обернем g[y] в Unevaluated - в обоих случаях мы дадим оценщику команду пропустить оценка g[y]:

In[63]:= f[Unevaluated[g[y]]]

Out[63]= Sin[Cos[y]]

Удерживающие атрибуты

Это связано с предыдущей точкой: нужно знать, что поиск UpValues выполняется даже внутри головок с атрибутами Hold - и, следовательно, определения, основанные на UpValue, могут оцениваться, даже если аналогично выглядящие DownValue - не будут. Пример:

In[64]:= ClearAll[f,ff];
f[x_]:=Print["Evaluated"];
ff/:h_[ff[x_]]:=Print["Evaluated"];

In[67]:= Hold[f[1]]
Out[67]= Hold[f[1]]

In[68]:= Hold[ff[1]]
During evaluation of In[68]:= Evaluated

Если вы хотите полностью предотвратить поиск UpValues, нужно дать функцию атрибуту HoldAllComplete. Например:

In[69]:= {HoldComplete[f[1]],HoldComplete[ff[1]]}
Out[69]= {HoldComplete[f[1]],HoldComplete[ff[1]]}

Ограничение глубины тега Level-1

Это уже упоминалось @rcollyer. Это ограничение было введено для эффективности анализатора-шаблона/оценщика. Я просто хочу подчеркнуть одно важное и довольно неочевидное следствие этого: похоже, вы не можете использовать UpValues для перегрузки присвоения (Set operator), чтобы он работал на переменные, назначенные объектам определенного типа. вводить. Вот попытка:

In[74]:= 
ClearAll[a,myType,myCustomCode,newValue];
myType/:Set[var_myType,rhs_]:=myCustomCode;

Это похоже на работу. Но давайте попробуем:

In[79]:= a = myType[1, 2, 3];
a = newValue;
a

Out[81]= newValue

Он не делает то, что мы хотим, очевидно. Проблема в том, что Set содержит свои l.h.s., поэтому к моменту совпадения шаблонов он имеет только символ a, а не его значение. И поскольку мы не можем связать определение с тегами глубже, чем на первом уровне выражения, следующее не будет работать:

ClearAll[a,myType,myCustomCode,newValue];
myType/:Set[var_,rhs_]/;MatchQ[var,_myType]:=myCustomCode;
TagSetDelayed::tagpos: Tag myType in (var_=rhs_)/;MatchQ[var,_myType]
  is too deep for an assigned rule to be found. >>

Насколько мне известно, UpValues не может быть использован для решения этой проблемы, что очень жаль, поскольку было бы удобно использовать обычный синтаксис = с кодом пользовательского назначения для разных типов данных. Для аналогичного обсуждения см., Например, этот. Эта ситуация не уникальна для Set - то же самое можно было бы использовать для любой функции, которая содержит аргумент, который вы хотите использовать для определения UpValue.

Некоторые различия между UpSet и TagSet, UpSetDelayed и TagSetDelayed

Стоит знать, что при использовании UpSet или UpSetDelayed все теги на уровне 1 приобретают дополнительные определения (правила). Например:

Clear[a,b];
Plus[a,b]^:=1;

?a
Global`a
a/:a+b:=1

?b
Global`b
b/:a+b:=1

В отличие от этого, TagSet и TagSetDelayed точнее:

ClearAll[a,b];
a/:Plus[a,b]:=1;

?a
Global`a
a/:a+b:=1

?b
Global`b

По моему опыту, последнее поведение обычно более желательно, поэтому в большинстве случаев я предпочитаю TagSet или TagSetDelayed более UpSet или UpSetDelayed.

Ответ 3

Rcollyer уже дал отличный ответ, но вот пример того, когда вы можете использовать UpValues: когда вы определяете конкретную структуру данных со своим собственным Head, и вы хотите определить, как встроенные операции арифметическая работа с этой структурой. Я как-то сделал это для структуры данных timeSeries, где, например, добавление соответствовало датам в первых столбцах и добавляло бы соответствующие пары значений во втором столбце. Добавление векторов T * 2 с датами в первом столбце дало бы бессмысленные даты, если бы вы не определили такую ​​операцию с помощью UpValue.