Можно ли заблокировать OwnValues, когда DownValues ​​уже существует? - PullRequest
9 голосов
/ 02 августа 2011

Для случаев, когда уже назначены значения DownValues, связанные с именем «a», существует ли приемлемый способ заблокировать присвоение OwnValues ​​одному и тому же имени?(Первоначально я сталкивался с этой проблемой, играя с чьей-то попыткой реализовать словарь данных.)

Вот что я хочу избежать:

Remove[a];
a[1] := somethingDelayed
a[2] = somethingImmediate;
DownValues[a]
a[1]
a[2]

Возвращает ...

{HoldPattern[a[1]] :> somethingDelayed, 
 HoldPattern[a[2]] :> somethingImmediate}
somethingDelayed
somethingImmediate

А теперь, если нам нужно оценить:

a = somethingThatScrewsUpHeads;
(* OwnValues[a] above stored in OwnValues *)
a[1]
a[2]

Мы получаем ...

somethingThatScrewsUpHeads[1]
somethingThatScrewsUpHeads[2]

Существует ли простой / гибкий способ предотвращения OwnValues ​​для любого имени в DownValues?(Дай-ка угадай ... это возможно, но произойдет хит производительности?)

Ответы [ 2 ]

12 голосов
/ 03 августа 2011

Я не знаю, является ли это "принятым" способом, но вы могли бы определить правило, которое не позволяет Set и SetDelayed действовать на a:

Remove[a];
a[1] := somethingDelayed
a[2] = somethingImmediate;

a /: HoldPattern[(Set|SetDelayed)[a, _]] := (Message[a::readOnly]; Abort[])

a::readOnly = "The symbol 'a' cannot be assigned a value.";

С этимПравило на месте, любая попытка назначить OwnValue для a не удастся:

In[17]:= a = somethingThatScrewsUpHeads;

During evaluation of In[17]:= a::readOnly:
  The symbol 'a' cannot be assigned a value.

Out[17]= $Aborted

In[18]:= a := somethingThatScrewsUpHeads;

During evaluation of In[18]:= a::readOnly:
  The symbol 'a' cannot be assigned a value.

Out[18]= $Aborted

Однако это правило все равно будет разрешать новый DownValues для a:

In[19]:= a[3] = now;
         a[4] := later

In[20]:= a[3]

Out[20]= now

In[21]:= a[4]

Out[21]= later

Производительность

Правило, по-видимому, не оказывает заметного влияния на производительность Set и SetDelayed, предположительно, поскольку правило установлено как повышающее значение дляa.Я попытался проверить это, выполнив ...

Timing@Do[x = i, {i, 100000000}]

... как до, так и после установки правила.Там не было никаких заметных изменений в сроках.Затем я попытался установить связанные с Set значения up для 10000 сгенерированных символов, таким образом:

Do[
  With[{s=Unique["s"]}
  , s /: HoldPattern[(Set|SetDelayed)[s, _]] :=
      (Message[s::readOnly]; Abort[])
  ]
, {10000}]

Опять же, время не изменилось даже при наличии большого количества правил up-value.Эти результаты показывают, что этот метод приемлем с точки зрения производительности, хотя я настоятельно рекомендую выполнять тесты производительности в контексте вашего конкретного приложения.

9 голосов
/ 02 августа 2011

Мне неизвестен какой-либо способ напрямую "заблокировать" OwnValues, и поскольку оценщик Mathematica оценивает головы раньше всего (части, применение DownValues, UpValues и SubValues и т. Д.), Это делаетникуда нас не приведет (я кратко обсудил эту проблему в своей книге ).

Проблема с простым подходом состоит в том, что он, вероятно, будет основан на добавлении DownValues к Set иSetDelayed, так как похоже на , они не могут быть перегружены через UpValues ​​.

РЕДАКТИРОВАТЬ

Как указано @WReach в комментариях, для рассматриваемого случая можно успешно использовать UpValues, поскольку мы имеем дело с Symbol sкоторый должен буквально присутствовать в Set / SetDelayed, и поэтому глубина тега 1 достаточна.Мой комментарий более уместен для переопределения Set для некоторых заголовков, и когда необходимо разрешить сохранение выражений с этими заголовками в переменной (случаи, такие как Part присваивания или пользовательские типы данных, различаемые по заголовкам)

END EDIT

Однако добавление DownValues для Set и SetDelayed в большинстве случаев является причиной катастрофы ( этот поток очень показателен),и должен использоваться очень редко (если вообще) и с особой осторожностью.

Из менее экстремальных подходов, возможно, самый простой и безопасный, но не автоматический способ - Protectсимволы после того, как вы их определили.У этого метода есть проблема в том, что вы не сможете добавлять новые или изменять существующие определения без Unprotect -ing символа.

В качестве альтернативы, а также для автоматизации вещей, вы можете использовать ряд приемов.Один из них - определить пользовательские операторы присваивания, такие как

ClearAll[def];
SetAttributes[def, HoldAll];
def[(op : (Set | SetDelayed))[lhs_, rhs_]] /; 
    Head[Unevaluated[lhs]] =!= Symbol || DownValues[lhs] === {} := 
         op[lhs, rhs]

и последовательно обернуть назначения SetDelayed - и Set в def (я выбрал этот синтаксис для def - сохранено Set / SetDelayed внутри def - для сохранения подсветки синтаксиса), и то же самое для Set.Вот как будет выглядеть ваш пример:

In[26]:= 
Clear[a];
def[a[1]:=somethingDelayed];
def[a[2]=somethingImmediate];
def[a=somethingThatScrewsUpHeads];

In[30]:= {a[1],a[2]}
Out[30]= {somethingDelayed,somethingImmediate}

Затем вы можете пойти дальше и написать макрос для обработки кода, который будет повсеместно переносить назначения на основе SetDelayed и Set в defв вашем коде:

SetAttributes[useDef, HoldAll];
useDef[code_] := ReleaseHold[Hold[code] /. {x : (_Set | _SetDelayed) :> def[x]}]

Таким образом, вы можете просто обернуть свой кусок кода в useDef, и тогда вам вообще не придется его менять.Например:

In[31]:= 
useDef[
   Clear[a];
   a[1]:=somethingDelayed;
   a[2]=somethingImmediate;
   a=somethingThatScrewsUpHeads;
]

In[32]:= {a[1],a[2]}
Out[32]= {somethingDelayed,somethingImmediate}

В интерактивном сеансе вы можете пойти еще дальше и установить $Pre = useDef, тогда вы не забудете свернуть свой код в useDef.

РЕДАКТИРОВАТЬ

Добавить диагностические возможности к def несложно, используя сопоставление с шаблоном.Вот версия, которая будет выдавать предупреждающее сообщение в случае попытки присвоения символа с DownValues:

ClearAll[def];
SetAttributes[def, HoldAll];
def::ownval = 
  "An assignment to a symbol `1` with existing DownValues has been attempted";
def[(op : (Set | SetDelayed))[lhs_, rhs_]] /; 
  Head[Unevaluated[lhs]] =!= Symbol || DownValues[lhs] === {} := op[lhs, rhs]
def[(Set | SetDelayed)[sym_, _]] := 
  Message[def::ownval, Style[HoldForm[sym], Red]];

Опять же, используя useDef[] (возможно, с $Pre), этоможет быть эффективным средством отладки, поскольку в исходный код вообще не требуется никаких изменений.

...