I really miss having something like a struct
in Mathematica. I know of (and regularly use) a couple of programming techniques which feel like a struct
(e.g., using downvalues), but are ultimately unsatisfactory (perhaps I'm using downvalues incorrectly). What programming approaches are available which provide similar functionality to a struct
?
Here's an abbreviated (and hopefully not too obtuse) example of how I use downvalues to emulate a struct. In this case, I'm distinguishing between TLC and TEC (these are sets of parameters for two different phases of a Moon mission, trans-lunar cruise and trans-earth cruise):
deadBandWidth[X][TLC] ^= 10. \[Degree];
deadBandWidth[Y][TLC] ^= 10. \[Degree];
deadBandWidth[Z][TLC] ^= 20. \[Degree];
sunSearchAngle[Z][TLC] ^= 230. \[Degree];
sunSearchRate[Z][TLC] ^= 1. \[Degree]/Second;
sunSearchAngle[X][TLC] ^= 75. \[Degree];
sunSearchRate[X][TLC] ^= 1. \[Degree]/Second;
safingSpinRate[TLC] ^= (360. \[Degree])/Day;
sunVector[TLC] ^= {-Cos[45. \[Degree]], 0., Sin[45. \[Degree]]};
safingSpinAxis[TLC] ^= sunVector[TLC];
deadBandWidth[X][TEC] ^= 20. \[Degree];
deadBandWidth[Y][TEC] ^= 20. \[Degree];
deadBandWidth[Z][TEC] ^= 20. \[Degree];
sunSearchAngle[Z][TEC] ^= 230. \[Degree];
sunSearchRate[Z][TEC] ^= 1. \[Degree]/Second;
sunSearchAngle[X][TEC] ^= 75. \[Degree];
sunSearchRate[X][TEC] ^= 1. \[Degree]/Second;
safingSpinRate[TEC] ^= (360. \[Degree])/Hour;
sunVector[TEC] ^= {0., 0., +1.};
safingSpinAxis[TEC] ^= sunVector[TEC];
?TLC
Global`TLC
safingSpinAxis[TLC]^={-0.707107,0.,0.707107}
safingSpinRate[TLC]^=6.28319/Day
sunVector[TLC]^={-0.707107,0.,0.707107}
deadBandWidth[X][TLC]^=0.174533
deadBandWidth[Y][TLC]^=0.174533
deadBandWidth[Z][TLC]^=0.349066
sunSearchAngle[X][TLC]^=1.309
sunSearchAngle[Z][TLC]^=4.01426
sunSearchRate[X][TLC]^=0.0174533/Second
sunSearchRate[Z][TLC]^=0.0174533/Second
Answer
Update: Mathematica 10 has introduced Association
, which can be used as a close equivalent of struct
s.
params = <| "par1" -> 1, "par2" -> 2 |>
params["par1"]
(* ==> 1 *)
In version 10 pure functions can have named arguments, and can be effectively used as expression templates where the slots can be populated from an association. This is similar to the technique I describe in the original version of this post (below the line).
#par1 + #par2 & [params]
will evaluate to 1 + 2
then to 3
.
That said, my personal workflow still fits better with the approach described below the line (withRules
). The reason for this is that I tend to build up calculations interactively and incrementally. This means that I do not start by writing the equivalent of an expression template (which would require thinking ahead...). Instead I start with all the values explicitly written out, and later I replace them with a global variable. This global variable can be simply Unset
, and given a local value using withRules
, then eventually changed into a function argument.
Quoting the OP's comment:
Most of the work I do involves constructing mathematical models and then testing various scenarios against those models. I'd like to be able to populate a particular scenario and then pass that scenario to a model. I'd also like to be able to copy that scenario, modify one or more parameters, and then pass the new scenario to the model.
The requirement, as I understand, is to be able to pass many parameter values around in a structured way. Lists of rules are convenient for this:
params = {par1 -> 1, par2 -> 2, par3 -> {x,y,z}}
They can be extracted like this:
par1 /. params
(* ==> 1 *)
Once I wrote a function for substituting such parameter lists into bigger pieces of code:
ClearAll[withRules]
SetAttributes[withRules, HoldAll]
withRules[rules_, expr_] :=
First@PreemptProtect@Internal`InheritedBlock[
{Rule, RuleDelayed},
SetAttributes[{Rule, RuleDelayed}, HoldFirst];
Hold[expr] /. rules
]
It can be used like this:
withRules[params,
par1 + par2
]
(* ==> 3 *)
withRules
can contain complex code inside, and all occurrences of par1
, par2
, etc. will be substituted with the values from the parameter list.
We can also write a function for easily modifying only a single parameter (from the whole list), and returning a new parameter list. Here's a simple implementation:
setParam[paramList_, newRules_] :=
DeleteDuplicates[Join[newRules, paramList],
First[#1] === First[#2] &]
Example usage:
setParam[params, {par2 -> 10}]
(* ==> {par2 -> 10, par1 -> 1, par3 -> {x, y, z}} *)
Another list which has a different value for par2
is returned.
If needed, this could be extended to support more complex, structured lists such as { par1 -> 1, group1 -> {par2x -> 10, par2y -> 20}}
, much how like the built-in option-handling works.
Addendum by celtschk: It's possible to extract a value from a list of rules using OptionValue
as well: OptionValue[params, par1]
.
Comments
Post a Comment