对于已经指定与名称“a”关联的DownValues的情况,是否有可接受的方法来阻止将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]
是否有一种简单/灵活的方法可以防止DownValues中任何名称的OwnValues? (Lemme猜......这是可能的,但会有性能受损吗?)
答案 0 :(得分:12)
我不知道这是否是“已接受”的方式,但您可以定义一条规则,阻止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}]
...安装规则之前和之后。时间上没有可观察到的变化。然后我尝试在10,000个生成的符号上安装Set
- 相关的up-值,因此:
Do[
With[{s=Unique["s"]}
, s /: HoldPattern[(Set|SetDelayed)[s, _]] :=
(Message[s::readOnly]; Abort[])
]
, {10000}]
同样,即使有如此多的高价值规则,时机也没有改变。这些结果表明,从性能的角度来看,这种技术是可以接受的,尽管我强烈建议您在特定应用的环境中进行性能测试。
答案 1 :(得分:9)
我不知道有什么方法可以直接“阻止”OwnValues
,因为Mathematica的评估者先评估头部(部分,DownValues
,UpValues
和{{1等等,这并没有把我们带到任何地方(我简要地讨论了这个问题in my book)。
直接方法的问题在于它可能基于将SubValues
添加到DownValues
和Set
,因为它看起来像they can not be overloaded via UpValues。
修改强>
正如@WReach在评论中指出的那样,因为我们正在处理SetDelayed
s UpValues
/ { Symbol
,因此标记深度Set
就足够了。我的评论与在某些头上重新定义SetDelayed
更相关,并且必须允许将具有这些头部的表达式存储在变量中(例如1
分配或由头部区分的自定义数据类型)
结束编辑
但是,在大多数情况下,为Set
和Part
添加DownValues
是灾难的处方(this线程非常具有说明性),应该使用非常很少(如果有的话)而且非常小心。
从不太极端的方法来看,也许最简单,最安全但不自动的方法是在定义它们之后Set
符号。此方法存在一个问题,即如果没有SetDelayed
符号,您将无法添加新的或修改现有定义。
或者,为了实现自动化,您可以使用许多技巧。一种是定义自定义赋值运算符,例如
Protect
并在Unprotect
中始终包含ClearAll[def];
SetAttributes[def, HoldAll];
def[(op : (Set | SetDelayed))[lhs_, rhs_]] /;
Head[Unevaluated[lhs]] =!= Symbol || DownValues[lhs] === {} :=
op[lhs, rhs]
- 和SetDelayed
分配(我为Set
选择了此语法 - 保留def
/ {{1} } 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
),这可以是一个有效的调试工具,因为根本不需要更改原始代码。