Для меня g /: f[g[x_]] := h[x] является просто многословным эквивалентом f[g[x_]] := h[x]. Можете ли вы привести пример, который вы должны использовать /:?
Что означает "upvalue" в Mathematica и когда их использовать?
Ответ 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.