示例和背景(注意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 []生成正确的数据。)
列昂尼德回答的总结。更改此代码
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];
答案 0 :(得分:4)
第一个问题的简短回答是您可能应该使用RuleDelayed
而不是Rule
,然后您不需要Hold
- ReleaseHold
。< / p>
由于您的代码示例不是自包含的,因此很难确定发生了什么。有一点可以肯定的是,OO-System使用上下文执行非平凡的操作,因为它使用上下文作为封装机制(这是有意义的)。通常,Rule
和RuleDelayed
会在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
有益的案例类别 - 它们只是一种解决方法,使用时并不是必需的延迟规则。