这个问题是关于在 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[]
中结构体的字段。并且更新将反射(reflect)在从函数调用返回的路上: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}
所以,
parms
在内部被覆盖/更新 foo[]
无需我告诉 M 通过 parms
引用 !我在我的程序中试过这个,我没有看到奇怪的副作用。 CDF 更新正常,没有错误。我不知道这是如何工作的,可能是 M 处理了
parms
里面的所有字段在这个例子中作为全局?但无论哪种情况,我都对此感到满意,因为它为我提供了一种将我的许多参数打包到结构中的方法,同时我能够在函数内部对结构进行更新。
但我的问题是:您认为这种方法存在重大问题吗?它在内部如何运作?我的意思是 M 如何在没有我的情况下处理这次传球
HoldFirst
或 Unevaluated
?我知道我现在失去了像以前一样进行参数检查的能力,但我不能拥有我想要的一切。正如我之前所说,M 需要一个真正的内置结构作为语言的一部分并集成到其中。但这是另一个时间来谈论。顺便说一句,到目前为止我看到的最好的结构仿真是由 Leonid Shifrin 在本主题末尾发布的 here但不幸的是,我无法在演示中使用它,因为它使用了演示 CDF 中不允许的符号。
谢谢
更新:
顺便说一句,这就是我认为 M 处理这个问题的方式。这只是我的猜测:我认为
param
是某种查找表,其字段仅用作其中的索引(可能是哈希表?)然后 param["p1"]
将在其中包含param["p1"]
的实际值所在的堆中位置的地址(不是值)。居住。像这样的东西:
所以,当通过
param
,然后在函数内部 foo[]
, 键入时 param["p1"]=u
会导致"p1"
指向的当前内存被释放,然后从堆中分配一个新的内存,其中的值u
被复制到。因此,返回时,这就是为什么我们看到
param["p1"]
的内容的原因。已经改变。但实际改变的是param["p1"]
地址指向的内存内容。代表。 (有一个名称,即 "p1"
,还有一个地址字段,指向名称 "p1"
所代表的内容)但这意味着,地址本身,即
"p1"
代表已更改,但查找名称本身(即“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]]
或使用 (param@u)[[1 ;; -1]]
我喜欢
(param@u)[[1 ;; -1]]
更多,这就是我现在用来访问索引对象中的列表的方法。书写方式
param@u
最接近我可以得到的标准记录符号 param.u
所以我现在对索引对象很满意,它似乎工作得很好。只需要注意几件小事。
最佳答案
我不打算回答低级数据结构的问题,因为我根本没有资格这样做。
您正在创建 "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}}
This shows how the data is stored in terms of high level Mathematica structures.
parms
is a global symbol. As you have observed, it can be safely "passed" without anything happening, because it is just a symbol without OwnValues
and nothing is triggered when it is evaluated. This is exactly the same as writing/evaluating foo
by itself.
The replacement that takes place in foo[thing_] := ...
is analogous to With
. If we write:
With[{thing = parms}, thing[x]]
thing
在 thing[x]
替换为 parms
之前 thing[x]
评估。同样,在上面的代码中我们得到 parms["p"] = u
之前 thing["p"]
或 Set
评估。当时,HoldFirst
Set
的属性接管,你会得到你想要的。由于您使用的是不可变字符串作为索引,因此它不会有更改的危险。只要您知道如何处理非本地化符号,例如
parms
那么我在使用这种方法时也没有看到新的危险。
关于wolfram-mathematica - 使用结构体通过引用传递参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8515405/