【问题标题】:Setting up a "struct" in Mathematica safely在 Mathematica 中安全地设置“结构”
【发布时间】:2011-09-09 02:58:08
【问题描述】:

关于在 Mathematica 中制作唱片的问题已经在少数地方讨论过,例如 Struct data type in Mathematica?

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

我的问题是:在 Mathematica 中是否有办法制作记录或结构,但又能够对单个元素使用上述检查?

我正在尝试使用一种方法,因为我厌倦了调用带有 10 个参数的函数(有时无法避免这种情况),即使我尝试使每个函数都非常具体,以最小化参数的数量,有些函数只需要很多参数就可以完成特定的工作。

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

方法一

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]

方法二

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' 或 'to' 在某处被覆盖,都会导致问题,如

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 拥有的一件事,它是帮助管理和组织程序中使用的所有变量的真实记录。

【问题讨论】:

标签: wolfram-mathematica


【解决方案1】:

首先,我想提一下,您列出的所有方法都存在 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]; 

请注意,我在构造函数和设置器中添加了检查,以说明如何做到这一点。有关如何使用以这种方式构建的结构的更多详细信息,您可以在我提到的帖子和那里找到的更多链接中找到。

您的示例现在将显示为:

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]

请注意,这种方法的主要优点是正确封装了状态,隐藏了实现细节,并且不会危及范围。

【讨论】:

  • Leonid,有一天我希望看到一个相当大的 Mathematica 应用程序的代码,该应用程序将你的许多概念组合在一起,这样我就可以看到你的方法如何影响“大型编程。”
  • @Mr.Wizard 取决于你所说的相当大的东西,但我确实有一些正在开发中。总有一天,我会发布它们。
  • 请注意,在 Mathematica 10 中添加的Association 结构基本上解决了这个问题。见my answer
  • @JessRiedel 我不同意。它只解决了部分问题。无论如何,我不明白这条评论的意义:你真的认为我不知道关联,或者它们如何在这种情况下使用?
  • 是的,当您在 2011 年 9 月撰写和最后编辑这篇文章时,我认为您不知道 Associations,因为直到 Mathematica 10 才引入 Associations。 Nasser 原始问题的哪一部分使用Associations(或DataSet)没有解决?
【解决方案2】:

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 模式命名(这将是this techniqueAssociation 模拟列表)

plotLimitType=<|"lowerLimit"->_NumericQ, "upperLimit"->_NumericQ|>

因为关联是原子的(ish)。见here

顺便提一下,像“lowerLimit”这样的键不需要用引号引起来。使用这种风格

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

同样有效。


有关详细信息,请参阅

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多