何时在Mathematica中使用Hold / ReleaseHold?

时间:2011-07-15 11:09:41

标签: wolfram-mathematica

示例和背景(注意Hold,ReleaseHold的用法):

以下代码表示用于创建scenegraph对象的静态工厂方法(来自XML文件)。 (output-)字段是CScenegraph的一个实例(OO-System类)。

 new[imp_]:= Module[{
 ret,
 type = "TG",
 record ={{0,0,0},"Root TG"}
 },
 ret = MathNew[
    "CScenegraph", 
    2,
    MathNew["CTransformationgroup",1,{type,record},0,0,0,0,Null]];
 ret@setTree[ret];
 ret@getRoot[]@setColref[ret];
 csp = loadClass["CSphere"];
 spheres = Cases[imp, XMLElement["sphere", _, __], Infinity];
 codesp = Cases[spheres, XMLElement["sphere", 
    {"point" -> point_, "radius" -> rad_, "hue" -> hue_}, {}] -> Hold[csp@new[ToExpression[point], ToExpression[rad], ToExpression[hue]]]];
 ret@addAschild[ret@getRoot[],ReleaseHold[codesp]];
 ret
 ];

我的问题是关于以下内容:

spheres = Cases[imp, XMLElement[\sphere\, _, __], Infinity];
codesp = Cases[spheres, XMLElement[\sphere\, 
    {\point\ -> point_, \radius\ -> rad_, \"hue\" -> hue_}, {}] -> Hold[csp@new[ToExpression[point], ToExpression[rad], ToExpression[hue]]]];
ret@addAschild[ret@getRoot[],ReleaseHold[codesp]];

其中

  addAschild 

将(几何)列表添加到(根)转换组并具有签名

  addAsChild[parent MathObject, child MathObject], or
  addAsChild[parent MathObject, Children List{MathObject, ...}]

表示球体的XML元素如下所示:

  <sphere point='{0., 1., 3.}'
  radius='1'
  hue='0.55' />

如果我不使用Hold [],ReleaseHold []我最终会遇到像

这样的对象数据
  {"GE", {"SP", {CScenegraph`point, CScenegraph`rad}}, {CScenegraph`hue}}

虽然我本来期待

  {"GE", {"SP", {{4., 3., -4.}, 3.}}, {0.45}}

(以上代码使用Hold [],ReleaseHold []生成正确的数据。)

问题

1。为什么在这种情况下保持必要? (事实上​​,是吗?有没有一种方法可以在没有Hold [],ReleaseHold []的情况下编写代码?)(我通过反复试验得到了它!不明白为什么。)

2。作为学习要点:使用Hold / ReleaseHold的原型示例/案例是什么?

编辑:

列昂尼德回答的总结。更改此代码

  codesp = Cases[spheres, XMLElement["sphere", 
{"point" -> point_, "radius" -> rad_, "hue" -> hue_}, {}] -> Hold[csp@new[ToExpression[point], ToExpression[rad], ToExpression[hue]]]];
  ret@addAschild[ret@getRoot[],ReleaseHold[codesp]];

为:

  codesp = Cases[spheres, XMLElement["sphere", 
{"point" -> point_, "radius" -> rad_, "hue" -> hue_}, {}] :> csp@new[ToExpression[point], ToExpression[rad], ToExpression[hue]]];
  ret@addAschild[ret@getRoot[],codesp];

1 个答案:

答案 0 :(得分:4)

第一个问题的简短回答是您可能应该使用RuleDelayed而不是Rule,然后您不需要Hold - ReleaseHold。< / p>

由于您的代码示例不是自包含的,因此很难确定发生了什么。有一点可以肯定的是,OO-System使用上下文执行非平凡的操作,因为它使用上下文作为封装机制(这是有意义的)。通常,RuleRuleDelayed会在r.h.s.中注入匹配的表达式,因此不清楚这是如何发生的。这是一种可能的场景(您可以在笔记本中执行此操作):

BeginPackage["Test`"]
f[{a_Symbol, b_Symbol}] := {c, d};
fn[input_] :=  Cases[input, XMLElement[{"a" -> a_, "b" -> b_}, {}, {}] -> f[{a, b}]];
fn1[input_] := Cases[input, XMLElement[{"a" -> a_, "b" -> b_}, {}, {}] :> f[{a, b}]];
EndPackage[];
$ContextPath = DeleteCases[$ContextPath, "Test`"]

现在,

In[71]:= Test`fn[{XMLElement[{"a"->1,"b"->2},{},{}],{"a"->3,"b"->4},{"a"->5,"b"->6}}]
Out[71]= {{Test`c,Test`d}}

由于我们在Rule中使用XMLElement[...]->rhs,因此r.h.s.在替换发生之前进行评估 - 在这种情况下,函数f会进行评估。现在,

In[78]:= Test`fn1[{XMLElement[{"a" -> 1, "b" -> 2}, {}, {}], 
      {"a" ->3, "b" -> 4}, {"a" -> 5, "b" -> 6}}]

Out[78]= {Test`f[{1, 2}]}

此处的结果有所不同,因为成语XMLElement[...] :> rhs用于实现fn1,这次涉及RuleDelayed。因此,在f[{a,b}]a被l.h.s.中的匹配数字替换之前,b未进行评估。由于f没有针对2个数字列表形式的参数的规则,因此会返回。

使用Hold - ReleaseHold的方法工作的原因是这会阻止r.h.s. (我的示例中的函数f,以及对原始函数中new的调用)的评估,直到模式变量的值被替换为它。作为旁注,您可能会发现向构造函数添加更好的错误检查很有用(如果OO-System允许的话),这样可以在运行时更好地诊断这样的问题。

所以,最后一行:使用RuleDelayed,而不是Rule


要回答第二个问题,组合ReleaseHold - Hold通常在您希望在允许评估之前操纵所保留的代码时非常有用。例如:

In[82]:= 
{a,b,c}={1,2,3};
ReleaseHold[Replace[Hold[{a,b,c}],s_Symbol:>Print[s^2],{2}]]

During evaluation of In[82]:= 1
During evaluation of In[82]:= 4
During evaluation of In[82]:= 9

Out[83]= {Null,Null,Null}

人们可能会提出更明智的例子。这对于代码生成这样的东西特别有用 - 可以找到一个不那么简单的例子here。正如我已经提到的那样,具体案例并不属于Hold - ReleaseHold有益的案例类别 - 它们只是一种解决方法,使用时并不是必需的延迟规则。