124
$\begingroup$

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. °;
deadBandWidth[Y][TLC] ^= 10. °;
deadBandWidth[Z][TLC] ^= 20. °;
sunSearchAngle[Z][TLC] ^= 230. °;
sunSearchRate[Z][TLC] ^= 1. °/Second;
sunSearchAngle[X][TLC] ^= 75. °;
sunSearchRate[X][TLC] ^= 1. °/Second;
safingSpinRate[TLC] ^= (360. °)/Day;
sunVector[TLC] ^= {-Cos[45. °], 0., Sin[45. °]};
safingSpinAxis[TLC] ^= sunVector[TLC];

deadBandWidth[X][TEC] ^= 20. °;
deadBandWidth[Y][TEC] ^= 20. °;
deadBandWidth[Z][TEC] ^= 20. °;
sunSearchAngle[Z][TEC] ^= 230. °;
sunSearchRate[Z][TEC] ^= 1. °/Second;
sunSearchAngle[X][TEC] ^= 75. °;
sunSearchRate[X][TEC] ^= 1. °/Second;
safingSpinRate[TEC] ^= (360. °)/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
$\endgroup$
13
  • 9
    $\begingroup$ I could search around for what a struct is, but it'd be nice if you can link to a description of it... :) $\endgroup$ Commented Jan 30, 2012 at 14:47
  • 4
    $\begingroup$ Can you explain what you want to do with this "struct"? Instead of pointing to a C feature, please explain what kind of things you want to do with this object. I know C, but I'm still not sure what feature of structs you are looking for in Mathematica. Also note that Mathematica shines when using immutable structures while I have the impression that you are looking for something mutable here. $\endgroup$
    – Szabolcs
    Commented Jan 30, 2012 at 14:58
  • 7
    $\begingroup$ Your example contains many UpValues, not DownValues. Also, it could you explain why you are using these assignments, why do you think they're advantageous? Again, the question is: what is the advantage of structs for you for this application? Then we can think about how to reproduce this advantage in Mathematica. $\endgroup$
    – Szabolcs
    Commented Jan 30, 2012 at 15:06
  • 2
    $\begingroup$ Also consider datatype[field1, field2, ...]. More MMA style, easier to manipulate, you can check for data type as _datatype, etc. You can always define a couple of constants if you want to access the fields like instance[[field1]]. Anyway, tere are many approaches, each with its advantages and disadvantages I guess $\endgroup$
    – Rojo
    Commented Jan 30, 2012 at 15:13
  • 6
    $\begingroup$ @Cassini So you just need something like a structured parameter list? That does not need to be a mutable variable. How about a list of rules, like tlc = {deadBandWidth -> {10,10,20}, sunSearchAngle -> 230}? If you just need to pass data around, then it is best not do make any definitions at all. $\endgroup$
    – Szabolcs
    Commented Jan 30, 2012 at 15:21

7 Answers 7

83
$\begingroup$

Update: Mathematica 10 has introduced Association, which can be used as a close equivalent of structs.

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].

$\endgroup$
15
  • 1
    $\begingroup$ I think as soon as you start thinking about modifying a single element (setParam), your approach looses expressiveness, because within it this operation is not natural. If you go further down that road (I was actually doing this too, for a while), you end up with another emulation of mutable structures, and not the best one, because you are copying stuff (e.g. you have copy - internally - and traverse the entire parameter list to modify a single element - so your assignemnt is strictly speaking not constant time). $\endgroup$ Commented Jan 30, 2012 at 15:50
  • $\begingroup$ @Leonid, well, but these lists tend to be rather small, so performance is not an issue. At least not for the things I was using them for. This does not become slow if we have a large parameter value, only if we have a large number of parameters. It's like Append. $\endgroup$
    – Szabolcs
    Commented Jan 30, 2012 at 15:53
  • 4
    $\begingroup$ I don't think it should be called "quick and dirty". I think this is the best solution here. It has an added advantage that you can just add new rules modifying your parameters in a batch, and rule-application mechanism will take care of the rest. You can, if you wish, factor this into a separate function. But what I'd do within this approach, instead, is to define configurations. The key to expressiveness is composition. Here is what I'd do: config[newrules__]@config[rules__] := config[newrules, rules];. It's really that simple, and we can assign these to variables and get ... $\endgroup$ Commented Jan 30, 2012 at 16:37
  • 1
    $\begingroup$ Just one observation (which doesn't seem to have been made above): With the definition of params as list of rules you also can extract (but not set) a specific value using OptionValue[params, par1]. $\endgroup$
    – celtschk
    Commented May 25, 2012 at 12:54
  • 1
    $\begingroup$ @mcandril: I've just now seen your comment. In case it's still relevant for you: I think simply using //. instead of /. would solve that issue. For example, {a,b} /. { a->1, b->x+a } gives {1, a + x}, but {a,b} //. { a->1, b->x+a } gives {1, 1 + x}. $\endgroup$
    – celtschk
    Commented Jul 16, 2014 at 11:53
56
$\begingroup$

This answer may be unacceptable right from the outset because it uses undocumented functions. However, it has advantages over some of the approaches suggested so far which might be redeeming enough in certain scenarios to recommend it in practice. In particular, it provides totally encapsulated state (unlike, e.g., DownValues or Temporary symbols) and O(1) access and updates (unlike, e.g., a list of rules).

I would suggest a System`Utilities`HashTable object, which exists in at least Mathematica 7 and 8 (but not in 5.2, and I didn't check 6). This is manipulated using a relatively small number of simple functions:

  • System`Utilities`HashTable[]: creates a new hash table.
  • System`Utilities`HashTableAdd[ht, key, val]: adds a key-value pair {key, val} to the hash table ht.
  • System`Utilities`HashTableGet[ht, key]: given a hash table ht and a key key, retrieves the value corresponding to key.
  • System`Utilities`HashTableRemove[ht, key]: given a hash table ht and a key key, removes key from ht.
  • System`Utilities`HashTableContainsQ[ht, key]: given a hash table ht and a key key which may or may not exist in ht, determines whether or not key does in fact exist in ht. (This is useful since adding a key that already exists or querying/removing a nonexistent key produces an ugly message.)

I trust that this is all quite self-explanatory, but the following is a brief usage example for reference:

h = System`Utilities`HashTable[]
 (* -> System`Utilities`HashTable[<0>] *)

(* Setting properties for an "account" *)
System`Utilities`HashTableAdd[h, accountID, 47];
System`Utilities`HashTableAdd[h, balance, 1632.40];

(* Querying a property *)
accid = System`Utilities`HashTableGet[h, accountID]
 (* -> 47 *)

(* Updating a property *)
bal = System`Utilities`HashTableGet[h, balance];
System`Utilities`HashTableRemove[h, balance];
System`Utilities`HashTableAdd[h, balance, bal + 506.31];

System`Utilities`HashTableGet[h, balance]
 (* -> 2138.71 *)

If you aren't completely put off by the fact that all of this is undocumented, the System`Utilities`HashTable looks it could offer a passable alternative to a struct for many applications.

$\endgroup$
8
  • 1
    $\begingroup$ It's not the CDF format or plugin that forbids the use of these functions, it is just the check of the demonstrations site. You could very well use these functions (unsupported as they probably are...) in a CDF document, it it will be running in the CDF Player as well as the Player Pro. $\endgroup$ Commented Jan 31, 2012 at 9:06
  • $\begingroup$ Nice answer! Hash tables are not quite structs, but this is a valuable piece of information. And great that you joined. +1. $\endgroup$ Commented Jan 31, 2012 at 9:34
  • 2
    $\begingroup$ Is it possible to update a value without removing it first? $\endgroup$
    – Ajasja
    Commented Apr 12, 2012 at 13:06
  • 3
    $\begingroup$ The functions still exist in Mathematica 9 $\endgroup$ Commented Mar 5, 2013 at 16:43
  • 1
    $\begingroup$ @Ajasja This is possible with the new HashTable data structure. $\endgroup$
    – Alan
    Commented May 31, 2023 at 14:58
37
$\begingroup$

There were several attempts to emulate structs in Mathematica. Emphasis on emulate, since AFAIK there is no built - in support for it yet. One reason for that may be that structs are inherently mutable, while idiomatic Mathematica gravitates towards immutability. You may find these discussions interesting:

Struct-data-type-in-mathematica

Object-oriented-mathematica-programming

Question-on-setting-up-a-struct-in-mathematica-safely

Mathematica-oo-system-or-alternatives

My own take on it is in this answer:

Tree-data-structure-in-mathematica

where I describe one possible emulation of structs, which I use every now and then when I need something like a struct (this is, of course, a personal preference. There are many ways to do this). It looks to be somewhat similar to your method. For a recent use case where I put similar approach to heavy use and where it really pays off (because structs are not the bottleneck there), see this answer, where I use this as an encapsulation mechanism for file-backed lists.

That said, a built-in support for mutable structures would be, I think, very desirable. Three major reasons I could think of, why various emulation approaches did not really take off:

  • Performance. Structs are the work-horse of data structures, and their performance is critical. OTOH, all emulations which are to be general, are bound to use the top-level code, and that is slow.
  • Garbage collection. The available ways to create encapsulated mutable state almost always involve creating definitions for symbols, and those definitions are frequently not automatically amenable to garbage collection
  • (The lack of) standardization. If there were a single emulation which would accumulate a significant code base, tools and practices of using it, that may have been different.
$\endgroup$
10
  • $\begingroup$ I've read about mutable and immutable several times on your posts and I get it only half of the way. I didn't study computer science, but googling tells me that mutable is when it can be changed after its creation, which Part can do it seems. On the other hand, you can change a downvalue without changing the others if you consider the pack of that symbols' definitoins as your structure. Also, wouldn't something like a tree built with node[left, data, right] with HoldAll be mutable? $\endgroup$
    – Rojo
    Commented Jan 30, 2012 at 15:22
  • $\begingroup$ I guess it's all about knowing when MMA does an internal copy of the expressions, which I don't know, nor know how to check... This reminds me that I've been confused with this issue also in the context of reading that Bags were so special because they could emulate a pointer, and a few things about linked lists (these I understand a little bit more than half of the way) $\endgroup$
    – Rojo
    Commented Jan 30, 2012 at 15:23
  • 1
    $\begingroup$ @Rojo What I mean is that if I have, say in C, a struct like typedef struct{ int x, int y} mystruct;, then, if s is of this type, I can say s.x = 10. If I have an expression like node[left, data, right], and it is stored in some variable (say expr), then I also can say expr[[1]] = 10, but I can not nest this deeper. For example, if left is also a node, I can not say left = expr[[1]]; left[[1]] = 10 and expect this change to also affect the original left node inside expr - it only affects a copy. Basically, it boils down to the fact that we lack here pointer semantics. $\endgroup$ Commented Jan 30, 2012 at 15:36
  • $\begingroup$ My guess is that Part on the lhs is the only way to change only part of an expression without making a copy. Also, that MMA only makes a copy of the data whe it has no other choice in its built-in functions that modify an expression. But how to know if, e.g, h=ReplacePart[h, 2->8] does a full copy or if its smart enough? (h being unpacked array) $\endgroup$
    – Rojo
    Commented Jan 30, 2012 at 15:46
  • $\begingroup$ @Rojo my point is that even Part is not enough, because elements of an expression are expressions, not pointers. When you extract them, their copy is made. When you assign these parts to variables and modify those, that assigned copy is modified, not the original part within a larger expression. As for ReplacePart, it sure does make a copy, as most other functions returning expressions. $\endgroup$ Commented Jan 30, 2012 at 15:53
33
$\begingroup$

Edit: The ideas below have led to a package called MTools. Instructions on how to install and use it are here: MTools tutorial.

Using symbols to store data and object-like functions

Here are interesting functions to use symbols like objects. (I originally posted these thoughts in What is in your Mathematica tool bag?).

The post has grown quite big over time as I used it to record ideas.

It's divided into three parts, one describing the function Keys, another one where properties and functions are stored in a symbol created inside a Module, thus mimicking objects in object oriented programming and a last one where objects have the form ObjectHead[object].

Introduction

It is already well known that you can store data in symbols and quickly access them using DownValues.

(*Write/Update*)
mysymbol["property"]=2;
(*Access*)
mysymbol["property"]
(*Delete*)
Unset[mysymbol["property"]]

It is similar to a hashtable, new rules are added for each property to DownValues[mysymbol]. But internally, from what I understood, rules of a symbol are stored as a hashtable so that Mathematica can quickly find which one to use. The key ("property" in the example) doesn't need to be a string, it can be any expression (which can be used to cache expressions, as also shown in the post quoted above).

Keys

You can access the list of keys (or properties) of a symbol using these functions based on what dreeves once submitted (I was quite lucky to have found his post early in my Mathematica learning curve, because it allowed me to work on functions working with lots of different arguments, as you can pass the symbol containing the stored properties to a function and see which keys this symbol contains using Keys):

SetAttributes[RemoveHead, {HoldAll}];
RemoveHead[h_[args___]] := {args};
NKeys[_[symbol_Symbol]]:=NKeys[symbol]; (*for the head[object] case*)
NKeys[symbol_] := RemoveHead @@@ DownValues[symbol(*,Sort->False*)][[All,1]];
Keys[symbol_] := Replace[NKeys[symbol], {x_} :> x, {1}];

Usage example of Keys

a["b"]=2;
a["d"]=3;
Keys[a]

(*getting the values associated with the keys of the a symbol*)
a /@ Keys[a]

If you use multiple keys for indexing a value

b["b",1]=2;
b["d",2]=3;
Keys[b]

(*getting the values associated with the keys of the b symbol*)
b @@@ Keys[b]

PrintSymbol

I use this function a lot to display all infos contained in the DownValues of a symbol (which uses one key per value):

PrintSymbol[symbol_] :=
  Module[{symbolKeys=Keys[symbol]},
    TableForm@Transpose[{symbolKeys, symbol /@ symbolKeys}]
  ];

PrintSymbol[a]

Replacing a part of a list stored in a symbol

The following would produce an error

mysymbol["x"]={1,2};
mysymbol["x"][[1]]=2

One way to do this would be either to introduce a temporary variable for the list stored in mysymbol["x"], modify it and put it back in mysymbol["x"] or, if possible, use a syntax like

mysymbol["x"] = ReplacePart[mysymbol["x"], 1 -> 2]

Interestingly some answers to this post How to Set parts of indexed lists? deal with this issue in a O(1) way (compared to the O(n) complexity of ReplacePart where a new list is created to modify it afterwards).

Creation of objects with integrated functions

Finally here is a simple way to create a symbol that behaves like an object in object oriented programming, different function syntaxes are shown :

Options[NewObject]={y->2};
NewObject[OptionsPattern[]]:=
  Module[{newObject,aPrivate = 0,privateFunction},
    (*Stored in DownValues[newObject]*)
    newObject["y"]=OptionValue[y];
    newObject["list"] = {3, 2, 1};

    (*Private function*)
    privateFunction[x_]:=newObject["y"]+x;

    (*Stored in UpValues[newObject]*)
    function[newObject,x_] ^:= privateFunction[x];
    newObject /: newObject.function2[x_] := 2 newObject["y"]+x;

    (* "Redefining the LessEqual operator" *)
    LessEqual[newObject,object2_]^:=newObject["y"]<=object2["y"];

    (* "Redefining the Part operator" *)
    Part[newObject, part__] ^:= newObject["list"][[part]];

    (*Syntax stored in DownValues[newObject], could cause problems by 
      being considered as a property with Keys*)
    newObject@function3[x_] := 3 newObject["y"]+x;

    (*function accessing a "private" variable*)
    functionPrivate[newObject] ^:= aPrivate++;

    (* "Redefining the [ ] operator" *)
    newObject[x_] := x newObject["list"];

    (*Format*)
    Format[newObject,StandardForm]:="newObject with value y = "~~ToString[newObject["y"]];

    newObject
  ];

Properties are stored as DownValues and methods as delayed Upvalues (except for the [ ] redefinition also stored as DownValues) in the symbol created by Module that is returned. I found the syntax for function2 that is the usual OO-syntax for functions in Tree data structure in Mathematica.

Private variable

The variables aPrivate can be seen as a private variable as it is only seen by the functions of each newObject (you wouldn't see it using Keys). Such a function could be used to frequently update a list and avoid the issue of the previous paragraph (Replacing a part of a list stored in a symbol).

If you wanted to DumpSave newObject you could know which aPrivate$xxx variable to also save by using the depends function of Leonid Shifrin described in the post Automatically generating a dependency graph of an arbitrary Mathematica function?.

depends[NewObject[]]

Note that xxx is equal to $ModuleNumber - 1 when this expression is evaluted inside Module so this information could be stored in newObject for later use.

Similarly the function privateFunction can be seen as an internal function that cannot be called explicitely by the user.

Other way for storing functions in a different symbol

You could also store the function definition not in newObject but in a type symbol, so if NewObject returned type[newObject] instead of newObject you could define function and function2 like this outside of NewObject (and not inside) and have the same usage as before. See the second part of the post below for more on this.

(*Stored in UpValues[type]*)
function[type[object_], x_] ^:= object["y"] + x;
type /: type[object_].function2[x_] := 2 object["y"]+x;

(*Stored in SubValues[type]*)
type[object_]@function3[x_] := 3 object["y"]+x;

Usage example

x = NewObject[y -> 3]
x // FullForm

x["y"]=4
x@"y"

function[x, 4]
x.function2[5]
x@function3[6]

(*LessEqual redefinition test with Sort*)
z = NewObject[]
{x["y"],z["y"]}
l = Sort[{x,z}, LessEqual]
{l[[1]]["y"],l[[2]]["y"]}

(*Part redefinition test*)
x[[3]]

(*function accessing a "private" variable*)
functionPrivate[x]

(*[ ] redefinition test*)
x[4]

Reference/Extension

For a list of existing types of values each symbol has, see http://reference.wolfram.com/mathematica/tutorial/PatternsAndTransformationRules.html and http://www.verbeia.com/mathematica/tips/HTMLLinks/Tricks_Misc_4.html.

You can go further if you want to emulate object inheritance by using a package called InheritRules available here http://library.wolfram.com/infocenter/MathSource/671/

Further ideas when storing functions in a head symbol

This second part of the post uses some ideas exposed previously but is independent, we redevelop equivalent ideas in a slightly different framework.

The idea is to use DownValues for storing properties in different symbols corresponding to objects and UpValues for storing methods in a unique head symbol (MyObject in the example below). We then use expressions of the form MyObject[object].

Here is a summary of what I currently use.

Constructor

Options[MyObject]={y->2};
MyObject[OptionsPattern[]]:=
   Module[{newObject,aPrivate = 0},
      newObject["y"]=OptionValue[y];
      newObject["list"] = {3, 2, 1};

      (*Private function*)
      privateFunction[newObject]^:=aPrivate++;

      MyObject[newObject]
   ];

MyObject is used as "constructor" and as head of the returned object (for example MyObject[newObject$23]). This can be useful for writing functions that take into account the head of an object. For example

f[x_MyObject] := ...

Properties (like the value corresponding to the key "y") are stored as DownValues in a newObject symbol created by Module whereas functions will be stored in the MyObject symbol as UpValues.

Private variable

(*function accessing a "private" variable*)
functionPrivate[MyObject[newObject_]] ^:= privateFunction[newObject];

In order to have a function accessing a private variable of newObject, aPrivate, a function stored as UpValues of newObject, privateFunction, is defined at the creation of newObject, and another function stored as UpValues of MyObject, functionPrivate, calls privateFunction.

Some methods stored as UpValues in MyObject (different syntaxes are shown)

(*Stored in UpValues[MyObject]*)
function[MyObject[object_], x_] ^:= object["y"] + x;
MyObject/: MyObject[object_].function2[x_] := 2 object["y"]+x;

(*Another cool syntax*)
o_MyObject.function4[x_] ^:= o.function2[x];

(* "Redefining the LessEqual operator" *)
LessEqual[MyObject[object1_],MyObject[object2_]]^:=object1["y"]<=object2["y"];

(* "Redefining the Part operator" *)
Part[MyObject[object_], part__] ^:= object["list"][[part]];

myGet[MyObject[object_], key_] ^:= object[key];
mySet[MyObject[object_], key_, value_] ^:= (object[key]=value);  
(*or*) 
MyObject/: MyObject[object_].mySet[key_, value_] := (object[key]=value);  

Note: the function4 syntax stores a rule in both MyObject and function4. The syntax is nonetheless convenient, and works well when several different classes have different function4 definitions.

Methods stored as SubValues in MyObject

A method stored to easily access the properties of an object. We restrict here key to be a string in order not to interfere with other functions defined as SubValues.

MyObject[object_Symbol][key_String] := object[key];

Another function stored in SubValues[MyObject]

MyObject[object_]@function3[x_] := 3 object["y"]+x;

Redefinition of the [ ] operator

MyObject[object_][x_] := x object["list"];

"Static" variable

Similarly to what is used for a private variable, a variable can be shared among all the objects of a similar class using a following definition for the function that accesses it. (Such variables use the keyword static in C++-like languages)

Module[{staticVariable=0},
   staticFunction[MyObject[object_]]^:=(staticVariable+=object["y"]);
]

Using methods from another class

Let's say that Class1 and Class2 share a common method named function. If we have an object Class1[class1Object] and want to use the function version of Class2 we can do this using something like

Class2[class1Object].function[]

Format

You can format the way the object is displayed with something like this:

Format[MyObject[object_Symbol],StandardForm]:="MyObject with value y = "~~ToString[object["y"]];

Creating an object

x = MyObject[y->3]

Test of the different functions

x // FullForm

function[x, 2]
x.function2[3]
x.function4[3]
x@function3[4]

x["y"]
x@"y"

(*LessEqual redefinition test with Sort*)
z = MyObject[]
{x["y"],z["y"]}
l = Sort[{x,z}, LessEqual]
{l[[1]]["y"],l[[2]]["y"]}

(*Part redefinition test*)
x[[3]]

(*function accessing a "private" variable*)
functionPrivate[x]

(*[ ] redefinition test*)
x[4]

(*static function example*)
staticFunction[x]
staticFunction[z]

Update properties

Using ObjectSet

To update the "y" property of z you can use this (or use a setter function like mySet defined above)

ObjectSet[(_[symbol_Symbol]|symbol_),key_,value_]:=symbol[key]=value;
ObjectSet[z,"y",3]

If an object is of the kind MyObject[object] then value will be assigned to object[key] (DownValues of object) instead of being assigned to MyObject[object][key] (SubValues of MyObject whereas I want functions to be in general stored as UpValues of MyObject and properties as DownValues of object).

Using object in MyObject[object] directly

Another way that doesn't involve another function is to do

z[[1]]["y"] = 4

Using mySet (defined above)

z.mySet["y",5]

Using Set

You can automate ObjectSet by overloading Set in a dynamic environment for example. See this post for more details Alternative to overloading Set

ClearAll[withCustomSet];
SetAttributes[withCustomSet, HoldAll];
withCustomSet[code_] :=
    Internal`InheritedBlock[{Set},
        Unprotect[Set];
        Set[symbol_[key_],value_]:=
           Block[{$inObjectSet=True},
          ObjectSet[symbol,key,value]
	   ]/;!TrueQ[$inObjectSet];
        Protect[Set];

        code
    ];

So that you can do

withCustomSet[
   z["y"] = 6
]
function[z, 2]

This syntax works also for sub-objects

withCustomSet[
   z["u"]=MyObject[];
   z["u"]["i"]=2
]

PrintSymbol[z["u"]]
$\endgroup$
7
  • $\begingroup$ I'd like to suggest that you migrate the info from your toolbag answer here, as the toolbag question has been closed and its not likely to be re-opened. So, eventually the info will be deleted. $\endgroup$
    – rcollyer
    Commented Jan 30, 2012 at 16:50
  • 7
    $\begingroup$ @Faysal Please, for the sake of salvation of all souls who will be lost after reading this part, don't overload Set! $\endgroup$ Commented Feb 2, 2012 at 1:14
  • 5
    $\begingroup$ @Faysal The problem is, at least in my view, that we, as advanced users, have increased responsibility towards less experienced ones, and community as a whole. The technique you described is dangerous, and not only to those who decide to use it. As soon as more user-contributed code (by us say) becomes widely used, this may become a real problem. People may start using this stuff in packages, and this may lead to all sorts of problems. Also, what I first saw was the code, and I saw your warning only upon the second read. Adding rules to Set is a very appealing idea, but IMO it is just wrong. $\endgroup$ Commented Feb 2, 2012 at 9:58
  • 1
    $\begingroup$ @Faysal One way to make this more bearable is through local environments, the approach I am pushing forward in many of my answers. You could create dynamic environments (say, with Iternal`InheritedBlock localizing Set), or, better yet, lexical environments. There are many problems with overloading Set via DownValues, but the major one IMO is that it is a non-local, system- wide change for something very frequently used. Environments help localize changes, and are a great tool for that. $\endgroup$ Commented Feb 2, 2012 at 10:20
  • 2
    $\begingroup$ Faysal you are a compulsive editor! :-) $\endgroup$
    – Mr.Wizard
    Commented Oct 22, 2012 at 13:47
24
$\begingroup$

The answers already posted show that built-in Mathematica functionality can be used to get the meaningful functionality provided by a C struct. If you want your code to be readable by other Mathematica users, I suggest using a list of rules as already advised above.

However, if you really want struct-style syntax I'll offer an implementation that I've found useful.

Features of a struct that are slightly different than a list of rules:

  1. Limited ordered data set. All instances of a particular struct type contain exactly the set of fields specified in the struct type declaration. It is impossible to add fields that aren't part of the struct declaration, or to be missing fields that are.
  2. Minimal storage. Each instance of a struct contains only the struct type name and the field values. It does not contain the list of field names - those names are stored only once and associated with the struct type name.

Example usage

Declare a structure type named "toad" that contains three fields. Two fields must match a pattern, the third is unrestrictied. The declaration is associated with the symbol "toad".

In[]:= DeclareStruct[toad, {{legLength, _Real}, {legColor, _RGBColor}, otherData}]

Define one instance of the "toad" struct with initial values for each field, given as a list of rules.

In[]:= myReptile = DefineStruct[toad,
  {otherData -> "Ted the frog", legLength -> 4.5, legColor -> RGBColor[0, 1, 0]}]
Out[]= Struct[toad,
  {legLength -> 4.5, legColor -> RGBColor[0, 1, 0], otherData -> "Ted the frog"}]

The actual storage for one instance of the struct does not include the field names. The per-instance storage includes only the field values and the struct name. The relationship between field names and field positions is associated with the struct name, not embedded in each instance of the struct.

In[]:= FullForm[myReptile]
Out[]= Struct[toad, List[4.5`, RGBColor[0, 1, 0], "Ted the frog"]]

To get values from the struct, use the LongRightArrow operator -- an operator which has no built-in meaning in Mathematica. LongRightArrow can be entered with Esc-->Esc.

In[]:= myReptile-->legColor
Out[]= RGBColor[0, 1, 0]

Field values can also be set with the LongRightArrow operator. Set is overloaded with an UpValue for LongRightArrow.

In[]:= myReptile-->legColor = RGBColor[0.5, 1, 0]
Out[]= RGBColor[0.5, 1, 0]

The implementation won't allow you to get or set a field that was not declared as a member of the struct, or set a field value to something that does not match the declared pattern.

In[]:= myReptile-->headSize = 6.0;
LongRightArrow::member: Field headSize is not a member of struct toad >>

Notes

  • Implementation handles nested structs.
  • Implementation does not handle assignment to parts of a field with mystruct->field[[n]]=val, though this could be added. Currently you must get the existing field value, modify part of it with ReplacePart, and assign the new value into the field.
  • Implementation avoids making local copies of objects by always modifying top-level symbols by part.
  • Cost to get a part is similar to a simple list of rules. Costs one rule replace to find the index, then some O(1) extra work for error checking and the part access by index.

Implementation

ClearAll[Struct]
Struct::usage = 
  "Struct objects contain a limited set of elements with minimal \
   storage overhead.  Struct types are declared with DeclareStruct and \
   struct objects are created with DefineStruct.";
Format[Struct[sy_, dt_]] := 
  "Struct"[ToString[sy], 
    If[ListQ[sy[Names]] && Length[sy[Names]] === Length[dt], 
      MapThread[Rule, {sy[Names], dt}], dt]]

ClearAll[DeclareStruct]
DeclareStruct::usage = 
  "DeclareStruct[structname, {fieldname..}] declares a structure \
   datatype named structname with the given field names.  Each field \
   name is a symbol or a list {symbol, pattern}";
DeclareStruct::error = 
  "DeclareStruct internal error.  Failed to handle argument error.";
DeclareStruct::argb = 
  "DeclareStruct called with argument count of `1`; 2 arguments are \
   expected.";
DeclareStruct::structname = "Struct name `1` must be a Symbol.";
DeclareStruct::fieldnames = 
  "Each field name in `1` must be a symbol or {symbol, pattern}.";
DeclareStruct[sy_Symbol, fld : {(_Symbol | {_Symbol, _}) ..}] := 
 Module[{fields = Replace[fld, a_Symbol :> {a, _}, {1}]},
  ClearAll[sy];
  sy[Names] = First /@ fields;
  sy[Pattern] = Last /@ fields;
  sy[Order] = MapIndexed[#1 -> First[#2] &, sy[Names]];]
DeclareStruct[] := Null /; Message[DeclareStruct::argb, 0]
DeclareStruct[sy_, ar___] := 
 Module[{ll}, 
  Null /; Which[ll = 1 + Length[{ar}]; ll =!= 2, 
    Message[DeclareStruct::argb, ll], Head[sy] =!= Symbol, 
    Message[DeclareStruct::structname, sy],
    !MatchQ[ar, {(_Symbol | {_Symbol, _}) ..}], 
    Message[DeclareStruct::fieldnames, ar],
    True, Message[DeclareStruct::error]]]

ClearAll[DefineStruct]
DefineStruct::usage = 
  "DefineStruct[structname, {fieldvaluerules}] returns an instance of \
   a structname struct, previously declared with DeclareStruct.";
DefineStruct::error = 
  "DefineStruct internal error.  Failed to handle argument error.";
DefineStruct::argb = 
  "DefineStruct called with argument count of `1`; 2 arguments are \
expected.";
DefineStruct::structname = "Struct name `1` must be a Symbol.";
DefineStruct::fieldrules = 
  "Field value rules `1` must be a list of rules giving values for \
   field symbols.";
DefineStruct::undef = 
  "Struct name `1` has not yet been declared with DeclareStruct.";
DefineStruct::setmatch = 
  "Set of field names `1` does not match the field names of declared \
   struct `2`";
DefineStruct::pattern = 
  "Value(s) in the field rules `1` don't match the pattern(s) `2` \
   provided to DeclareStruct for struct `3`";
DefineStruct[sy_Symbol, rl : {(_Symbol -> _) ..}] := 
 Module[{vl}, 
  Struct[sy, vl] /; 
   ListQ[sy[Names]] && (Sort[First /@ rl] === 
      Sort[sy[Names]]) && (vl = Replace[sy[Names], rl, {1}]; 
     MatchQ[vl, sy[Pattern]])]
DefineStruct[] := Null /; Message[DefineStruct::argb, 0]
DefineStruct[sy_, ar___] := 
 Module[{ll}, 
  Null /; Which[ll = 1 + Length[{ar}]; ll =!= 2, 
    Message[DefineStruct::argb, ll], Head[sy] =!= Symbol, 
    Message[DefineStruct::structname, sy],
    !MatchQ[ar, {(_Symbol -> _) ..}], 
    Message[DefineStruct::fieldrules, ar], ! ListQ[sy[Names]], 
    Message[DefineStruct::undef, sy], ll = First /@ ar; 
    Sort[ll] =!= Sort[sy[Names]], 
    Message[DefineStruct::setmatch, ll, sy], 
    ll = Replace[sy[Names], ar, {1}]; ! MatchQ[ll, sy[Pattern]], 
    ll = Transpose[
      Select[Transpose[{ll, sy[Pattern]}], ! 
         MatchQ[First[#1], Last[#1]] &]]; 
    Message[DefineStruct::pattern, First[ll], Last[ll], sy], True, 
    Message[DefineStruct::error]]]

ClearAll[LongRightArrow]
LongRightArrow::usage = 
  LongRightArrow::usage <> 
   "  struct\[RightArrow]field returns the value of field in struct.  \
    struct\[RightArrow]field=v sets the value of field in struct to v.";
LongRightArrow::member = "Field `1` is not a member of struct `2`";
LongRightArrow::pattern = 
  "Value `1` does not match pattern `2` for field `3` in struct `4`";
LongRightArrow[st_Struct, fl__Symbol] := 
 Module[{sy, ii, id = {}}, st[[Sequence @@ id]] /; (
    Scan[
     (sy = Part[st, Sequence @@ id, 1];
       ii = Replace[#1, sy[Order]];
       If[ii === #1, Message[LongRightArrow::member, #1, sy]; 
        Return[]];
       id = Join[id, {2, ii}]) &, {fl}];
    Length[id] === 2 Length[{fl}])]
LongRightArrow /: Set[LongRightArrow[st_Symbol, fl__Symbol], vl_] := 
 Module[{sy, ii, id = {}}, (
    Scan[
     (sy = Part[st, Sequence @@ id, 1];
       ii = Replace[#1, sy[Order]];
       If[ii === #1, Message[LongRightArrow::member, #1, sy]; 
        Return[]];
       id = Join[id, {2, ii}]) &, {fl}];
    Which[Length[id] =!= 2 Length[{fl}], vl,
     !MatchQ[vl, sy[Pattern][[ii]]], 
     Message[LongRightArrow::pattern, vl, sy[Pattern][[ii]], fl, sy]; 
     vl,
     True, With[{ij = Sequence @@ id}, st[[ij]] = vl]]) /;
   Head[st] === Struct]
$\endgroup$
9
  • $\begingroup$ Actually a struct in C or C++ also doesn't contain the type name, only the members. You cannot find out which type is stored there by just looking at the contents (except if you can guess the type from the contents). Only true class instances contain information about their type (in C++ via the virtual table pointer; C of course doesn't have them). $\endgroup$
    – celtschk
    Commented May 27, 2012 at 9:08
  • 2
    $\begingroup$ BTW, I like the use of LongRightArrow — that's an operator I wasn't previously aware of and which fits perfectly here (I wonder why Wolfram didn't use that for Java method calls instead of messing with the semantics — including associativity! — of @) $\endgroup$
    – celtschk
    Commented May 27, 2012 at 9:15
  • $\begingroup$ celtschk has a good point - that a C struct does not contain the type name. The Mma Struct needs it to get the transformation rules from field name to field position during evaluation of struct-->field. But this brings up what we could do with Compile. With a promise that we would not change a Struct declaration after compiling an expression, Compile could do the name->position transformation immediately, so at evaluation of the compiled expression the field would be accessed directly by index. $\endgroup$ Commented May 27, 2012 at 16:32
  • 1
    $\begingroup$ Actually you could provide the struct name at access time. Then the struct type definition would be just a list of rules mapping member names to list indices. The struct instance would just be a list, and struct access would be like toad[[legColor/.myReptile]]. $\endgroup$
    – celtschk
    Commented May 27, 2012 at 17:34
  • $\begingroup$ Absolutely. Your solution minimizes storage and is standard Mma (though I think you meant myReptile[[legColor/.toad]], relative to my example). But the (gratuitous) goal of this exercise was to mimic C struct syntax, which doesn't provide the type at access time. $\endgroup$ Commented May 28, 2012 at 0:07
21
$\begingroup$

I arrived very late to this party and I'm very much afraid that nobody comes here anymore. Still I'm posting this in hope that an occasional visitor may find it a practical approach to implementing data structures with named fields within Mathematica.

The concept

The idea is to use protected symbols to name a structure and its fields. The symbol that names the structure is also made orderless, so the fields are automatically maintained in canonical order. Protection is required to prevent both classes of symbols from being bound to a value; they must remain value-free for the approach described here to work.

Here is a semi-formal definition of a structure. Note that the fields are implemented as a sequence of rules. Replace will be used to both get and set the values of fields.


 structure ::= structName[field.1, ..., field.n]
 structName ::= "protected, orderless symbol"
 field.k ::= fieldName.k -> value.k
 fieldName.k ::= "protected symbol"

In my own work, I follow the convention that field names take the form structName$name. I find adhering to it makes programs more readable and easier to debug, but rejecting it will in no way jeopardize the general concept.

As with any implementation of data structures, this approach has both costs and benefits. The benefits are mostly realized during application development and maintenance; the costs are mostly incurred at run-time and paid in the coin of execution time and memory usage. For many applications I think the benefits gained outweigh the costs incurred.

Declaring structures

Setting the necessary attributes manually for each new structure type can get tedious very quickly. declare makes this job easier.

 declare[structName_Symbol, fieldNames : (_Symbol) ..] :=
    (SetAttributes[structName, {Orderless, Protected}];
     Protect[fieldNames];)

Examples of structures

 declare[data, data$x, data$y, data$z];
 declare[person, person$firstName, person$lastName];
 d = data[data$x -> 1, data$y -> 2, data$z -> 3];
 p = person[person$firstName -> "Brian", person$lastName -> "Smith"];

Since both data ans person are orderless, writing the fields in a different order does no harm.

 u = data[data$y -> 2, data$x -> 1, data$z -> 3];
 v = person[person$lastName -> "Smith", person$firstName -> "Brian"];
 {d == u, p == v}  (* ==> {True, True} *)

Functions for accessing and modifying fields

Access

get returns the value associated with the field named in the 2nd argument of the structure passed in as the 1st argument.

get[struct_, fieldName_Symbol] := fieldName /. List @@ struct

Quite often a subset or even all of the values in a structure are wanted. It shouldn't be necessary to write multiple get expressions to do this. get can be extended to accept a list of field names or the token All and return a list of the requested values.

get[struct_, fieldNames : {_Symbol ..}] := fieldNames /. List @@ struct

get[struct_, All] := With[{rules = List @@ struct},
                        ((First@#)& /@ rules) /. rules]
Modification

Mathematica essentially refuses to mutate objects, so set provides the illusion of modifying the field specified by its 2nd argument to have the value passed in as its 3rd argument. It's an illusion because the structure set returns is newly minted and not the structure passed in as its 1st argument.

set[struct_, fieldName_Symbol, val_] :=
   struct /. (fieldName -> _) -> fieldName -> val

assign works like set except the 1st argument passed to assign must be a symbol bound to a structure. set returns the value passed in as its 3rd argument. assign is provided to make it unnecessary to write code such as

d = set[d, data$x, 42]

because it makes the assignment within its code body.

 SetAttributes[assign, HoldFirst]
 assign[structName_Symbol, fieldName_Symbol, val_] :=
    (Unevaluated[structName] =
       structName /. (fieldName -> _) -> (fieldName -> val);
    val)

Factory functions

Although structure instances can be created by typing out the full expression for the instance, this can be tedious and error-prone, especially for structures that have many fields. In most cases it is better to provide one or more factory functions. My convention is to name all such function create and make them distinguishable to Mathematica by varying their argument patterns. Factory functions for different structure types are distinguishable because a structure name token is invariably passed as the 1st argument.

Factory functions can also be useful for modifying structures. When several fields in a structure instance require modification, successive applications of set or assign will create multiple copies of the instance, all of which are garbage. A factory function used for the same purpose will create just one garbage instance. But don't be too quick to reject set and assign. You must write each and every factory function you use; set and assign are universal and are always available.

Here is a completely trivial example of a factory function:

 create[person, first_String, last_String] :=
    person[person$firstName -> first, person$lastName -> last]

Here is one that is not so trivial:

 With[{pattern = Repeated[_String, {2}]},
    create[data, xName : pattern, yName : pattern, zName : pattern] :=
       data[data$x -> create[person, xName ],
            data$y -> create[person, yName ],
            data$z -> create[person, zName ]]]

Application

Anyone who has read this far would probably like to see a non-trivial example of structures with named fields. I think a Mathematica implementation of the famous X Window program xeyes will do.

According to the X Window System man page, xeyes was initially written by Jeremy Huxtable and shown at SIGGRAPH in 1988. It was ported to X11 by Keith Packard. It has been immensely popular ever since.

Irises and pupils

The iris and pupil of an eye will combined into a single structure called iris.


 iris[iris$center->center, iris$color->color, iris$radius->radius]
 center ::= {x, y}
 x ::= Real
 y ::= Real
 color ::= RGBColor[red, green, blue]
 radius ::= Real "radius of the iris"

declare[iris, iris$center, iris$color, iris$radius]

shape creates a graphics descriptor that can be supplied to a Graphics expressions to draw an iris. The pupil is drawn at half the diameter of the iris.

 shape[iris, i_iris] :=
    Module[{color, center, r},
       {center, color, r} = get[i, All];
       {{color, Disk[center, r]}, Disk[center, 0.5 r]}]

The iris factory function is intended to be called from within the eye factory function. An iris is created with a radius 0.3 of the radius of its containing eye and is initially placed at the eye's center.


 eyeXY ::= {eyeX, eyeY} "eye's center"
 eyeX ::= Real
 eyeY ::= Real
 eyeR ::= Real "radius of the eye"
 color ::= RGBColor[red, green, blue]
 Returns :: iris[...] "newly minted iris"

create[iris, eyeXY : {_Real, _Real}, eyeR_Real, color_RGBColor] :=
   iris[iris$center -> XY, iris$radius -> 0.3 eyeR, iris$color -> color]

Eyes


 eye[eye$center->center, eye$inR->r, eye$iris->i, eye$outR->R]
 center ::= {x, y}
 x ::= Real
 y ::= Real
 r ::= Real "radius of the circle on which the iris tracks"
 i ::= iris[...]
 R ::= Real "radius of the eye"

declare[eye, eye$center, eye$inR, eye$iris, eye$outR]

shape creates a graphics descriptor that can be supplied to Graphics expressions to draw an eye.

 shape[eye, e_eye] :=
    Module[{center, i, R},
      {center, i, R} = get[e, {eye$center, eye$iris, eye$outR}];
      {{FaceForm[White], EdgeForm[{Black, Thick}], Disk[center, R]},
           shape[iris, i]}]

The eye factory function.


 center ::= {x, y}
 x ::= Real
 y ::= Real
 R ::= Real "radius of the eye"
 r :: = Real "radius of the circle on which the iris tracks"
 color ::= RGBColor[red, green, blue] "iris color"
 Returns :: eye[...] "newly minted eye"

create[eye, center : {_Real, _Real}, R_Real, r_Real, color_RGBColor] :=
   Module[{i = create[iris, center, R, color]},
      eye[eye$center -> center, eye$inR -> r, eye$iris -> i, eye$outR -> R]]

Function for moving an eye's iris along its tracking circle.


 e ::= eye[...]
 theta ::= radians "angle iris center is to make with eye center
                    after iris is placed on tracking circle"
 Returns :: eye[...] "copy of e with iris placed on tracking circle"

 placeIrisAt[e_eye, theta_Real] :=
    Module[{center, r, i},
       {center, r, i} = get[e, {eye$center, eye$inR, eye$iris}];
       assign[i, iris$center, center + r {Cos[theta], Sin[theta]}];
       set[e, eye$iris, i]]

Function that makes an eye appear to be looking at the specified point.


 e ::= eye[...]
 pt ::= {x, y}
 x ::= Real
 y ::= Real
 Returns :: eye[...] "copy of e in which the iris is placed at the
                      intersection of the tracking circle and the
                      line through the eye center and pt"

lookAt[e_eye, pt : {_, _}] :=
   placeIrisAt[e, ArcTan @@ (pt - get[e, eye$center ])]

Mathematica Eyes

Create a pair of eyes having a given spacing and with the pair center at {x, y}. Place the eyes in a square containing a red dot. Make the eyes follow the dot as it is dragged around the square by the mouse. The Reset button will return the dot to its initial position.

 With[{box = {{-4., 4.}, {-4., 4.}}, spacing = 0.3, x = 2., y = 3.,
       R = 0.75, r = 0.45, color = RGBColor[0., 0.5, 1.],
       dotHome = {-2., -2.}},
    DynamicModule[{lf, rt, dot, dotXY = dotHome},
       dot = Locator[Dynamic@dotXY,
               Graphics[{Red, PointSize[Large], Point[dotXY]}]];
       lf = create[eye, {-(R + 0.5 spacing) + x, y}, R, r, color];
       rt = create[eye, {(R + 0.5 spacing) + x, y}, R, r, color];
       Dynamic@Refresh[lf = lookAt[lf, dotXY]; rt = lookAt[rt, dotXY];
          Column[{Framed@Graphics[{shape[eye, lf], shape[eye, rt], dot},
                            PlotRange -> box, ImageSize -> {400, 400}],
             Button["Reset", dotXY = dotHome, ImageSize -> 60]},
             Center],
          TrackedSymbols -> {dotXY}]]]

xeyes implemented in Mathematica

$\endgroup$
6
  • 1
    $\begingroup$ Interesting ideas, but what is the ::= notation ? It doesn't work for me. $\endgroup$
    – faysou
    Commented Nov 8, 2012 at 11:21
  • $\begingroup$ @FaysalAberkane. Look up Backus-Naur form on, say, Wikipedia. $\endgroup$
    – m_goldberg
    Commented Nov 8, 2012 at 14:23
  • $\begingroup$ Ok I looked at the code to quickly ::= is not meant to be executed, thanks. $\endgroup$
    – faysou
    Commented Nov 8, 2012 at 14:28
  • $\begingroup$ @FaysalAberkane. No, that stuff is documentation. In my original Mathematica notebook these cells are of a special, non-evaluatable type. Perhaps there is a better way to show them here. I'm new to this markdown stuff and couldn't figure out what was better. $\endgroup$
    – m_goldberg
    Commented Nov 8, 2012 at 14:39
  • $\begingroup$ @m_goldberg Regarding the "::=" notation which was confusion since it was in a Mathematica code-block, have you seen that you can use a different code-block language? Maybe marking it differently would have helped. Have you seen this? $\endgroup$
    – halirutan
    Commented Nov 26, 2012 at 4:13
17
$\begingroup$

So the naive way to set up a data structure like struct is, as the OP suggested, to simply used DownValues and/or SubValues. In the below, I use SubValues.

Copying the Wikipedia C language struct example

struct account {
   int account_number;
   char *first_name;
   char *last_name;
   float balance;
};

struct account s; // Create new account labelled s
s.account_number // access the account number

In Mathematica, we can talk about an "instance" of account as

account["s"]

set and access its properties using SubValues

account["s"]["account_number"] = 12345

account["s"]["account_number"]
(* Returns: 12345 *)

To make this a bit more robust, you should probably have a gentleman's agreement with your code to only access the "objects" using type checked instantiation and setting methods. Also, code for deletion of "objects" is easy to write by using DeleteCases on the SubValues of account. That said, I've written largish applications for my own use that do not bother with such niceties.

$\endgroup$
1
  • 1
    $\begingroup$ This comes up too often. If yo insist on using struct, program in C. If you decide to use Mathematica, read relevant portions of the documentation and you will see how the language is intended to be used. Then you should use it as Wolfram intended. You are like a Chinese person trying to use a knife and fork the same way they would use chop sticks. $\endgroup$
    – Ted Ersek
    Commented Jul 13, 2014 at 1:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.