在Mathematica中安全地设置“结构”

时间:2011-09-09 02:58:08

标签: wolfram-mathematica

关于在Mathematica中创建记录的问题已在少数几个地方进行过讨论,例如Struct data type in Mathematica?

所有这些方法的问题在于,人们似乎失去了这样做的能力 对每个参数进行特定的额外检查,例如x_?NumericQ

我的问题是:在Mathematica中有没有办法制作唱片或结构,但是能够在各个元素上使用上述检查?

我正在尝试使用一种方法来使用,因为我厌倦了使用10个参数调用函数(有时候无法避免这种情况),即使我尝试使每个函数非常具体,也要尽量减少参数的数量,有些函数只需要很多参数来完成特定的工作。

首先,我展示了我所知道的三种方法。

方法1

foo[p_]:=Module[{},
    Plot[Sin[x],{x,from/.p,to/.p}]
]
p={from->-Pi,to->Pi};
foo[p]

优点:安全,好像我将符号'从'更改为其他内容,它仍然有效。如下例所示。

foo[p_]:=Module[{},
    Plot[Sin[x],{x,from/.p,to/.p}]
]
p={from->-Pi,to->Pi};
from=-1; (* By accident the symbol from was set somewhere. It will work*)
foo[p]

方法2

Clear[p,foo];
foo[p_]:=Module[{},
    Print[p];
    Plot[Sin[x],{x,p["from"],p["to"]}]
]
p["from"] = -Pi;
p["to"]   = Pi;

foo[p]

优点:同样安全,字符串是不可变的。不必担心“从”值的变化。但到处都有字符串不太可读?

方法3

Clear[p,to,from];
foo[p_]:=Module[{},
    Plot[Sin[x],{x,p[from],p[to]}]
]
p[from] = -Pi;
p[to]   = Pi;

foo[p]

缺点:如果任何符号'从'或'到'某处被覆盖,将导致问题,如

from=-4; (*accidentally the symbol from is assigned a value*)
foo[p]   

因此。我认为方法(1)是最安全的。但现在我失去了这样做的能力:

foo[from_?NumericQ, to_?NumericQ] := Module[{},
    Plot[Sin[x], {x, from, to}]
]
from = -Pi; to = Pi;
foo[from, to]

所以,我希望有一个想法能够组合制作一个'记录',但同时,仍然能够使用记录中各个元素的参数检查?或者这个问题对于基于Mathematica功能/规则的编程风格是不是很好?

这是我希望Mathematica拥有的一件事,这是一个真正的记录,可以帮助管理和组织程序中使用的所有变量。

2 个答案:

答案 0 :(得分:10)

首先,我想提一下,你列出的所有方法都是IMO有缺陷和危险的。我不喜欢它们的主要原因是它们引入了对全局变量的隐式依赖(为什么这是坏的原因被讨论,例如here),并且还可能搞乱范围。它们的另一个问题是,这些方法看起来不会很好地适应同时存在的结构的许多实例。你列出的第二种方法似乎是最安全的,但它也有它的问题(字符串作为字段名称,无法对这样的结构进行类型检查,也可能意外地使用了符号值)。

在我的帖子here中,我讨论了构建可变数据结构的可能方法,其中方法可以进行额外检查。我将在这里复制相关文章:

Unprotect[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
ClearAll[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
Module[{first, second},
   first[_] := {};
   second[_] := {};
   pair /: new[pair[]] := pair[Unique[]];
   pair /: new[pair[],fst_?NumericQ,sec_?NumericQ]:= 
      With[{p=new[pair[]]}, 
          p.setFirst[fst];
          p.setSecond[sec];
          p];
   pair /: pair[tag_].delete[] := (first[tag] =.; second[tag] =.);
   pair /: pair[tag_].setFirst[value_?NumericQ] := first[tag] = value;
   pair /: pair[tag_].getFirst[] := first[tag];
   pair /: pair[tag_].setSecond[value_?NumericQ] := second[tag] = value;
   pair /: pair[tag_].getSecond[] := second[tag];       
];
Protect[pair, setFirst, getFirst, setSecond, getSecond, new, delete]; 

请注意,我在构造函数和setter中添加了检查,以说明如何完成此操作。有关如何使用以这种方式构造的结构的更多细节,您可以在我提到的帖子中找到并在那里找到更多链接。

您的示例现在为:

foo[from_?NumericQ, to_?NumericQ] :=
   Module[{}, Plot[Sin[x], {x, from, to}]];
foo[p_pair] := foo[p.getFirst[], p.getSecond[]]
pp = new[pair[], -Pi, Pi];
foo[pp]

请注意,此方法的主要优点是可以正确封装状态,隐藏实现细节,并且不会将范围置于危险之中。

答案 1 :(得分:0)

Mathematica 10引入了Association,它具有struct的许多最重要的属性(并且具有与您一直在尝试的替换规则类似的语法)。

plotLimits = <| "lowerLimit" -> -Pi, "upperLimit" -> Pi |>; 
(*this is the syntax for an Association[]*)

foo[p_]:=Module[{},
 Plot[Sin[x],{x,p["lowerLimit"],p["upperLimit"]}]
];
(* assoc["key"] is one of many equivalent ways to specify the data *)

我们也可以轻松地对参数进行检查

fooWithChecks[p_?(NumericQ[#["lowerLimit"]] && NumericQ[#["upperLimit"]] &)] := Module[{}, 
 Plot[Sin[x], {x, p["lowerLimit"], p["upperLimit"]}]
];

在这种情况下,foo[plotLimits]fooWithChecks[plotLimits]会给出相同的情节,因为plotLimits具有不错的数值。但是如果我们定义

badPlotLimits = <|"lowerLimit" -> bad, "upperLimit" -> Pi|>;

然后评估foo[badPlotLimits]会出错

Plot::plln: Limiting value bad in {x,<|lowerLimit->bad,upperLimit->2 \[Pi]|>[lowerLimit],<|lowerLimit->bad,upperLimit->2 \[Pi]|>[upperLimit]} is not a machine-sized real number. >>
Plot[Sin[x], {x, <|"lowerLimit" -> bad, "upperLimit" -> 2 \[Pi]|>["lowerLimit"], <|"lowerLimit" -> bad, "upperLimit" -> 2 \[Pi]|>["upperLimit"]}]

但是评估fooWithChecks[badPlotLimits]只是保持未评估,因为参数没有通过NumericalQ检查:

fooWithChecks[<|"lowerLimit" -> bad, "upperLimit" -> 2 \[Pi]|>]

我不清楚为什么要询问表格foo[from_?NumericQ, to_?NumericQ]而不是foo[p_?(someCheckFunction)]。首先拥有结构的一个关键好处是你可以重新组织结构存储在内存中的方式,比如交换“lowerLimit”和“upperLimit”的顺序,而无需重写任何使用它的函数(因为他们通过p["lowerLimit"]而不是p[[1]]来称呼它。如果您定义foo,那么在调用foo时,参数将按顺序推断,该功能会中断。 (换句话说,你阻止foo知道结构。)当然,你仍然可以这样做,也许是因为你也希望在非结构上使用foo

foo[from_?NumericQ, to_?NumericQ] :=
 Module[{}, Plot[Sin[x], {x, from, to}]];
foo[p] := foo[p["lowerLimit"], p["upperLimit"]];

如果你想要非常小心,可以使用它:

foo[p_?(SubsetQ[Keys[#],{"lowerLimit", "upperLimit"}]&)] :=
 foo[p["lowerLimit"], p["upperLimit"]];

不幸的是,你不能使用类似这样的内容为某些Association模式(列表的Association模拟plotLimitType=<|"lowerLimit"->_NumericQ, "upperLimit"->_NumericQ|> 模拟)提供名称

plotLimits = <|lowerLimit -> -Pi, upperLimit -> Pi|>;

因为关联是原子的(ish)。请参阅this technique

顺便说一下,请注意像“lowerLimit”这样的键不需要在引号中。使用这种风格

Association

同样适用。

有关详细信息,请参阅