使用结构通过引用传递参数

时间:2011-12-15 05:10:56

标签: wolfram-mathematica

这个问题是关于在M中通过引用的主题(我的相关问题在这里simple question on passing data between functions

虽然我试图在不使用Unevaluted[]HoldFirst[]的情况下找到一种通过引用传递内容的方法,但是我错误地使用了这种方法,它看起来对我很有用,即使我做了不明白它是如何工作的以及使用它的任何隐藏风险。所以我想在这里向专家展示,并询问他们是否认为使用是安全的(我有非常大的演示,需要将参数打包成不同结构的数量来帮助管理它们,这就是我的方式在我尝试的时候找到了这个方法。

以下是方法:首先我们知道无法编写以下内容:

Remove[p]
foo[p_] := Module[{u},
   u = Table[99, {10}];
   p = u
   ];

p = 0;
foo[p];

更新' p'在上面是改为呼叫成为

foo[Unevaluated@p];

或者使用foo[]定义HoldFirst

但这是我找到的方式,它通过引用传递,没有这些:

我将所有参数放在一个结构中(我现在仍然这样做),并传递结构,然后可以更新foo[]中结构的字段,更新将反映在返回的方式中来自函数调用:

Remove[parms]
foo[parms_] := Module[{u},
   u = Table[99, {10}];
   parms["p"] = u
   ];

parms["p"] = 0;
foo[parms];

现在,parms["p"]包含新列表{99, 99, 99, 99, 99, 99, 99, 99, 99, 99}

所以parmsfoo[]内被覆盖/更新,我不必告诉M通过引用传递parms

我在我的程序中试过这个,我看到没有奇怪的副作用。 CDF更新正常,没有错误。我不知道这是如何工作的,可能是M将此示例中parms内的所有字段视为全局字段?

但无论如何,我很满意这一点,因为它为我提供了一种方法来将我的许多参数打包成结构体,同时我能够在函数内部更新结构。

但我的问题是:您是否看到此方法存在重大问题?它在内部如何运作?我的意思是M如何在没有HoldFirstUnevaluated的情况下处理此传递?我知道我现在失去了像以前一样进行参数检查的能力,但我无法拥有我想要的一切。正如我之前所说,M需要一个真正的内置结构作为语言的一部分并集成到其中。但这是另一个时间来讨论。

顺便说一下,到目前为止我看到的最好的结构模拟是由Leonid Shifrin在这个帖子的末尾发布的here,但遗憾的是我不能在我的演示中使用它,因为它使用了符号不允许在演示CDF中使用。

感谢

更新 顺便说一下,这就是我认为M如何处理这个问题。这只是我的一个猜测:我认为param是某种查找表,它的字段只是用作它的索引(可能是一个哈希表?)然后param["p1"]将包含在其中param["p1"]的实际值所在的堆中的位置的地址(而不是值)。

这样的事情:

enter image description here

因此,当传递param时,然后在函数foo[]内,当键入param["p1"]=u时,它将导致"p1"指向的当前内存被释放,并且然后从堆中分配一个新内存,并在其中复制u的值。

因此,在返回时,这就是为什么我们看到param["p1"]的内容发生了变化。但实际发生了变化的是param["p1"]所代表的地址所指向的内存内容。 (有一个名称,"p1"和一个地址字段,指向名称"p1"代表的内容)

但是这意味着,"p1"表示的地址本身已经改变,但查找名称本身(即#34; p1")没有改变。

因此,由于名称本身没有改变,所以不需要使用HoldFirst,即使此名称所代表的数据被修改了吗?

更新

这个方法中有一个小故障,但解决它并不是什么大问题:使用这个索引对象方法传递事物时,事实证明人们无法修改对象的一部分。例如,以下内容不起作用:

foo[param_] := Module[{},
   param["u"][[3]] = 99 (*trying to update PART of u *)
   ];

param["u"] = Table[0, {5}];
foo[param];

上面给出了错误

Set::setps: "param[u] in the part assignment is not a symbol"

但是解决方法很简单。制作一个想要更新其中一部分的整个字段的本地副本,然后更新(部分)本地副本,然后将副本写回字段,如下所示

foo[param_] := Module[{u = param["u"]}, (* copy the whole field *)
   u[[3]] = 99;  (*update local copy *)
   param["u"] = u (*now update the field, ok *)
   ];

param["u"] = Table[0, {5}];
foo[param];

好。如果一个人可以更新部分领域本来会更好,所以没有特别的'需要处理。但至少周围的工作并不是那么糟糕。

更新 为了完整起见,我想我提到了另一个关于使用索引对象和解决方法的小事。

我写了

param[u] = {1, 2, 3}
param[u][[1 ;; -1]]

按预期返回{1,2,3}

然后我发现我可以使用param@u代替param[u],这真的有帮助,因为太多坚实的括号开始让我头疼。

但是当我输入

param@u[[1 ;; -1]]
希望能够像以前一样回答相同的答案,但事实并非如此。一个人得到一个错误,(我想我知道为什么,运算符优先级问题,但现在不是重点),只是想说解决方法很简单,可以使用param[u][[1 ;; -1]]或使用{{1} }

我更喜欢(param@u)[[1 ;; -1]],所以我现在正在使用它来访问索引对象中的列表。

符号(param@u)[[1 ;; -1]]尽可能接近标准记录符号param@u所以我现在对索引对象感到高兴,而且它似乎工作得非常好。只需要注意几件小事。

1 个答案:

答案 0 :(得分:5)

我不会回答低级别数据结构的问题,因为我根本没有资格这样做。

您正在创建"indexed objects"并使用不可变字符串作为索引。一般来说,我认为这些类似于哈希表。

以您的代码为例,让我们通过为foo的参数使用唯一名称来消除可能的歧义:

Remove[parms, foo]

foo[thing_] :=
  Module[{u},
    u = Table[99, {10}];
    thing["p"] = u
  ];

parms["p"] = 0;
foo[parms];

parms["p"]

DownValues[parms]
{HoldPattern[parms["p"]] :> {99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}

这显示了如何根据高级 Mathematica 结构存储数据。

parms是一个全球性的象征。正如您所观察到的,它可以安全地“传递”而不会发生任何事情,因为它只是一个没有OwnValues的符号,并且在评估时不会触发任何内容。这与单独编写/评估foo完全相同。

foo[thing_] := ...中的替换类似于With。如果我们写:

With[{thing = parms}, thing[x]]
thing评估之前,thing[x]中的

parmsthing[x]替换。同样,在上面的代码中,我们在parms["p"] = uthing["p"]评估之前获得Set。那时,HoldFirst的{​​{1}}属性接管了,你得到了你想要的东西。

由于您使用不可变字符串作为索引,因此不存在更改的危险。只要你知道如何处理非本地化的符号,例如Set,那么我在使用这种方法时也没有新的危险。