structure ::= structName[field.1, ..., field.n]
structName ::= "protected, orderlesorderless symbol"
field.k ::= fieldName.k -> value.k
fieldName.k ::= "protected symbol"
structure ::= structName[field.1, ..., field.n]
structName ::= "protected, orderles symbol"
field.k ::= fieldName.k -> value.k
fieldName.k ::= "protected symbol"
structure ::= structName[field.1, ..., field.n]
structName ::= "protected, orderless symbol"
field.k ::= fieldName.k -> value.k
fieldName.k ::= "protected symbol"
structure ::= structName[field.1, ..., field.n]
structName ::= "protected, orderles symbol"
field.k ::= fieldName.k -> value.k
fieldName.k ::= "protected symbol"
structure ::= structName[field.1, ..., field.n]
structName ::= "protected, orderles symbol"
field.k ::= fieldName.k -> value.k
fieldName.k ::= "protected symbol"
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"
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"
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"
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, XYeyeXY : {_Real, _Real}, eyeR_Real, color_RGBColor] :=
iris[iris$center -> XY, iris$radius -> 0.3 eyeR, iris$color -> color]
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"
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"
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"
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"
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"
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"
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"
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"
structure ::= structName[field.1, ..., field.n]
structName ::= "protected, orderles symbol"
field.k ::= fieldName.k -> value.k
fieldName.k ::= "protected symbol"
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"
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, XY : {_Real, _Real}, eyeR_Real, color_RGBColor] :=
iris[iris$center -> XY, iris$radius -> 0.3 eyeR, iris$color -> color]
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"
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"
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"
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"
structure ::= structName[field.1, ..., field.n]
structName ::= "protected, orderles symbol"
field.k ::= fieldName.k -> value.k
fieldName.k ::= "protected symbol"
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"
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]
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"
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"
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"
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"
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, orderles 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, XY : {_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}]]]