Skip to main content
Fixed typos
Source Link
m_goldberg
  • 108.1k
  • 16
  • 104
  • 259
 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"
Added <!-- language: none --> hints to the BNF blocks
Source Link
m_goldberg
  • 108.1k
  • 16
  • 104
  • 259
 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"
Source Link
m_goldberg
  • 108.1k
  • 16
  • 104
  • 259

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

xeyes implemented in Mathematica