Mathematica中赋值中不需要的求值:为什么会发生以及如何在包加载期间调试它



我正在开发一个不能正常加载的(大)包。这发生在我修改了一行代码之后。当我尝试加载包(需要)时,包开始加载,然后setdelayed定义之一"激活"(即。(以某种方式求值),被困在前几行加载的错误捕获例程中,并且包加载中止。
带有abort的错误捕获例程正在完成它的工作,只是在包加载阶段一开始就不应该调用它。错误消息显示,错误的参数实际上是一个模式表达式,我在几行之后的setdelayed定义的lhs上使用了它。

像这样:

……Some code lines
Changed line of code 
g[x_?NotGoodQ]:=(Message[g::nogood, x];Abort[])
……..some other code lines
g/: cccQ[g[x0_]]:=True

当我试图加载包时,我得到:

g::nogood: Argument x0_ is not good
如您所见,传递的参数是一个模式,它只能来自上面的代码行。

我试图找出这种行为的原因,但到目前为止我一直没有成功。所以我决定使用强大的Workbench调试工具。

我想一步一步地看到(或带有断点)加载包时会发生什么。我对WB还不太熟悉,但似乎,使用Debug作为…,首先加载包,然后最终用断点等进行调试。我的问题是,包甚至没有完全加载!在加载包之前设置的任何断点似乎都不有效。

所以…2个问题:

    谁能解释一下为什么这些代码行在包加载时"活起来"?(据我所见,包中没有明显的语法错误或代码片段)
  1. 谁能解释一下(如果)是如何可能检查/调试包代码,而在WB加载?

谢谢你的帮助。

编辑

根据列昂尼德的回答并使用他的EvenQ例子:我们可以通过在g

的下降值之前定义g的上升值来避免使用Holdpattern
notGoodQ[x_] := EvenQ[x];
Clear[g];
g /: cccQ[g[x0_]] := True
g[x_?notGoodQ] := (Message[g::nogood, x]; Abort[])
现在

?g
Global`g
cccQ[g[x0_]]^:=True

g[x_?notGoodQ]:=(Message[g::nogood,x];Abort[])

In[6]:= cccQ[g[1]]
Out[6]= True

,

In[7]:= cccQ[g[2]]
During evaluation of In[7]:= g::nogood: -- Message text not found -- (2)
Out[7]= $Aborted

所以…一般规则:

当编写函数g时,首先定义g的上值,然后定义g的下值,否则使用Holdpattern

你能同意这条规则吗?

列昂尼德说,使用Holdpattern可能表明改进的设计。除了上面指出的解决方案,如何改进上面的小代码的设计,或者更好地,在处理上值时?

谢谢你的帮助

撇开WB(这不是真正需要回答你的问题)-这个问题似乎有一个简单的答案,仅基于表达式在赋值期间的评估方式。下面是一个例子:

In[1505]:= 
notGoodQ[x_]:=True;
Clear[g];
g[x_?notGoodQ]:=(Message[g::nogood,x];Abort[])
In[1509]:= g/:cccQ[g[x0_]]:=True
During evaluation of In[1509]:= g::nogood: -- Message text not found -- (x0_)
Out[1509]= $Aborted

为了使其工作,我故意为notGoodQ定义了一个始终返回True的定义。那么,为什么g[x0_]是通过TagSetDelayed来评估的呢?答案是,虽然h/:f[h[elem1,...,elemn]]:=...中的TagSetDelayed(以及SetDelayed)不应用f可能具有的任何规则,但它将评估h[elem1,...,elem2]以及f。下面是一个例子:

In[1513]:= 
ClearAll[h,f];
h[___]:=Print["Evaluated"];
In[1515]:= h/:f[h[1,2]]:=3
During evaluation of In[1515]:= Evaluated
During evaluation of In[1515]:= TagSetDelayed::tagnf: Tag h not found in f[Null]. >>
Out[1515]= $Failed  

TagSetDelayedHoldAll的事实并不意味着它不求值它的参数——它只意味着参数到达它时没有求值,它们是否会被求值取决于TagSetDelayed的语义(我在上面简要描述过)。对于SetDelayed也是如此,所以通常使用的语句"不计算其参数"并不是字面上正确的。更正确的说法是,它接收未求值的参数,并以一种特殊的方式求值它们——不求值r.h.s,而对于l.h.s,求值head和元素,但不应用head的规则。为了避免这种情况,您可以在HoldPattern中进行包装,像这样:

Clear[g,notGoodQ];
notGoodQ[x_]:=EvenQ[x];
g[x_?notGoodQ]:=(Message[g::nogood,x];Abort[])
g/:cccQ[HoldPattern[g[x0_]]]:=True;

这个通过了。下面是一些用法:

In[1527]:= cccQ[g[1]]
Out[1527]= True
In[1528]:= cccQ[g[2]]
During evaluation of In[1528]:= g::nogood: -- Message text not found -- (2)
Out[1528]= $Aborted
但是,请注意,在定义时,需要在左侧使用HoldPattern,这通常表明,在函数调用期间,头部中的表达式也可能求值,这可能会破坏代码。下面是我的意思的一个例子:
In[1532]:= 
ClearAll[f,h];
f[x_]:=x^2;
f/:h[HoldPattern[f[y_]]]:=y^4;

这段代码试图捕捉像h[f[something]]这样的情况,但它显然会失败,因为f[something]会在h之前求值:

In[1535]:= h[f[5]]
Out[1535]= h[25]

对我来说,在l.h.s.上需要HoldPattern是我需要重新考虑我的设计的标志。

编辑

关于在WB中加载期间的调试,您可以做的一件事(IIRC,现在无法检查)是使用好的旧打印语句,其输出将出现在WB的控制台中。就我个人而言,我很少觉得需要调试器来实现这个目的(在加载时调试包)

编辑2

回答问题中的编辑:

关于定义的顺序:是的,你可以这样做,它解决了这个特殊的问题。但是,一般来说,这不是鲁棒的,我不认为这是一个好的通用方法。很难对手头的案例给出明确的建议,因为它有点脱离上下文,但在我看来,在这里使用UpValues是不合理的。如果这样做是为了错误处理,还有其他方法可以在不使用UpValues的情况下完成。

一般来说,UpValues最常用于以安全的方式重载某些函数,而不为被重载的函数添加任何规则。一个建议是避免将UpValues与同样具有DownValues并可能进行评估的头像联系在一起——通过这样做,你开始与评估者进行游戏,并最终会失败。最安全的方法是将UpValues附加到惰性符号(头部,容器)上,这些符号通常表示您希望在其上重载给定函数的对象的"类型"。

关于我对HoldPattern的存在表示糟糕设计的评论。HoldPattern当然有合法的用法,比如这个(有些人为的):

In[25]:= 
Clear[ff,a,b,c];
ff[HoldPattern[Plus[x__]]]:={x};
ff[a+b+c]
Out[27]= {a,b,c} 

在这里是合理的,因为在许多情况下Plus仍然是未求值的,并且在其未求值的形式下是有用的——因为可以推断出它表示一个和。我们在这里需要HoldPattern,因为Plus是在单个参数上定义的,而且在定义期间模式恰好是单个参数(尽管它通常描述多个参数)。因此,我们在这里使用HoldPattern来防止将模式视为正常参数,但这与Plus的预期用例主要不同。无论何时出现这种情况(我们确信该定义将适用于预期用例),HoldPattern都没问题。注意,这个例子也是脆弱的:

In[28]:= ff[Plus[a]]
Out[28]= ff[a]

大部分情况下仍然OK的原因是我们通常不会在单个参数上使用Plus

但是,还有第二种情况,通常提供的参数的结构与用于定义的模式的结构相同。在这种情况下,赋值期间的模式计算表明,在函数调用期间对实际参数进行相同的计算。你的用法就属于这一类。我对设计缺陷的评论是针对这样的情况—您可以防止模式求值,但您也必须防止参数求值,以使其工作。针对未完全求值表达式的模式匹配是脆弱的。此外,函数不应该为参数假定一些额外的条件(超出它可以进行类型检查的条件)。

最新更新