The question on making a record like in Mathematica has been discussed in few places, such as Struct data type in Mathematica?.
The problem with all these methods, i
Mathematica 10 has introduced Association, which has many of the most important properties of a struct
(and has similar syntax to the replacement rules you've been experimenting with).
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 *)
We can also easily implement checks on the arguments
fooWithChecks[p_?(NumericQ[#["lowerLimit"]] && NumericQ[#["upperLimit"]] &)] := Module[{},
Plot[Sin[x], {x, p["lowerLimit"], p["upperLimit"]}]
];
In this case, foo[plotLimits]
and fooWithChecks[plotLimits]
give the same plot, because plotLimits
has nice numerical values. But if we define
badPlotLimits = <|"lowerLimit" -> bad, "upperLimit" -> Pi|>;
then evaluating foo[badPlotLimits]
gives an error
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"]}]
but evaluating fooWithChecks[badPlotLimits]
just remain unevaluated since the argument doesn't pass the NumericalQ check:
fooWithChecks[<|"lowerLimit" -> bad, "upperLimit" -> 2 \[Pi]|>]
It's not clear to me why you ask about the form foo[from_?NumericQ, to_?NumericQ]
rather than foo[p_?(someCheckFunction)]
. A key benefit of having the struct in the first place is that you can reorganize how the struct is stored in memory, say by swapping the order of "lowerLimit" and "upperLimit", without re-writing any of the functions that use it (since they call it by p["lowerLimit"]
not p[[1]]
). That ability breaks if you define foo
such that, when foo
is called, the arguments are inferred by order. (In other words, you are preventing foo
from knowing about the structure.) You can still do it, of course, perhaps because you want to use foo
on non-structs too:
foo[from_?NumericQ, to_?NumericQ] :=
Module[{}, Plot[Sin[x], {x, from, to}]];
foo[p] := foo[p["lowerLimit"], p["upperLimit"]];
If you wanted to be really careful, you could use this:
foo[p_?(SubsetQ[Keys[#],{"lowerLimit", "upperLimit"}]&)] :=
foo[p["lowerLimit"], p["upperLimit"]];
Unfortunately, you can't gives names to certain Association
patterns (which would be the Association
analog of this technique for lists) using something like this
plotLimitType=<|"lowerLimit"->_NumericQ, "upperLimit"->_NumericQ|>
because Associations are atomic(ish). See here.
By the way, note that the keys like "lowerLimit" don't need to be in quotes. Using this style
plotLimits = <|lowerLimit -> -Pi, upperLimit -> Pi|>;
works just as well.
For more info, see
Association
)First, I'd like to mention that all the methods you listed are IMO flawed and dangerous. The main reason why I don't like them is that they introduce implicit dependences on global variables (the reasons why this is bad are discussed e.g. here), and can also mess up with the scoping. Another problem of them is that those approaches look like they won't scale nicely to many instances of your structs existing simultaneously. The second method you listed seems the safest, but it has its problems as well (strings as field names, no way to type-check such a struct, also symbols used there may accidentally have a value).
In my post here I discussed a possible way to build mutable data structures where methods can do extra checks. I will copy the relevant piece 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];
Note that I added checks in the constructor and in the setters, to illustrate how this can be done. More details on how to use the structs constructed this way you can find in the mentioned post of mine and further links found there.
Your example would now read:
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]
Note that the primary advantages of this approach are that state is properly encapsulated, implementation details are hidden, and scoping is not put in danger.