Для меня 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
.