MathGroup Archive 2010

[Date Index] [Thread Index] [Author Index]

Search the Archive

Re: Re: something nice I found today, return multiple

  • To: mathgroup at smc.vnet.net
  • Subject: [mg113167] Re: [mg113143] Re: something nice I found today, return multiple
  • From: Leonid Shifrin <lshifr at gmail.com>
  • Date: Sat, 16 Oct 2010 13:11:28 -0400 (EDT)
  • References: <i927an$3io$1@smc.vnet.net>

Here is my take on this issue. Sometimes structs are really a good option
either because of readability or because of the stateful nature of the
problem. Here is a sort of code which I use to generate structs with certain
properties:

Unprotect[new, delete, get, set, getFields];
ClearAll[new, makeStruct, delete, get, set, structExistsQ,
  destroyStruct, getFields];
Protect[new, delete, get, set, getFields];
Module[{structs, structValidQ, getProp, structFieldNames,
   releaseInstanceStorage, fieldNameCount},
  fieldNameCount[_] = 0;

  getProp[x_, prop_, f_: First, Hold[failCode_]] :=
   If[# === {},
      failCode,
      First@#] &@Cases[x, _prop, 1];

  structExistsQ[structName_Symbol] :=
   ValueQ[structFieldNames[structName]];

  destroyStruct::nostr = "The struct with the name `1` does not exist";
  destroyStruct[structName_Symbol?structExistsQ] :=
   With[{heldStructInstances =
      Hold[structs[structName]] /. DownValues[structs],
     fieldNames = structFieldNames[structName]},
    Map[releaseInstanceStorage[structName], structs[structName]];
    Unset @@ heldStructInstances;
    structs[structName] =.;
    structFieldNames[structName] =.;
    structValidQ[structName] =.;
    Map[fieldNameCount[#]-- &, fieldNames];
    With[{delfields = Select[fieldNames, fieldNameCount[#] == 0 &]},
     Unprotect @@ delfields;
     ClearAll @@ delfields;
     ]];

  destroyStruct[
    structName_Symbol] := (Message[destroyStruct::nostr,
     structName]; $Failed);

  makeStruct[structName_Symbol, fieldNames__Symbol] :=
   Module[{structInstances, getInstance},
    ClearAll[structName];
    structName::noprop = "No property with the name `1` found";
    structName::badst =
     "The structure has not been created with the <new> operator and \
is not valid.";
    structFieldNames[structName] = {fieldNames};
    With[{addFieldNames =
       Select[{fieldNames}, fieldNameCount[#] == 0 &]},
     Unprotect @@ addFieldNames;
     ClearAll @@ addFieldNames;
     SetAttributes[addFieldNames, HoldAll];
     Protect @@ addFieldNames];
    Map[fieldNameCount[#]++ &, {fieldNames}];

    structs[structName] = structInstances;
    getInstance[instanceRef_] := structInstances[First[instanceRef]];

    releaseInstanceStorage[structName][instance_] :=
     Cases[instance,
      (Alternatives @@ structFieldNames[structName])[y_Symbol] :>
       Remove[y]];

    structValidQ[structName] :=
     Function[instance,
      MatchQ[instance, structName[_Symbol]] &&
       MatchQ[getInstance[instance],
        structName @@ Map[#[_Symbol] &, {fieldNames}]]];

    structName /: structName.new[] :=
     With[{stateVars = Table[Unique[], {Length[{fieldNames}]}],
       objectId = Unique[]},
      structInstances[objectId] =
       structName @@ MapThread[Compose, {{fieldNames}, stateVars}];
      Evaluate[stateVars] = Table[Null, {Length[stateVars]}];
      structName[objectId]
      ];

    structName /: structName.getFields[] := {fieldNames};

    structName /: structName.getAllInstances[] :=
     Apply[structName, DownValues[structInstances][[All, 1]], {2}] /.
      Verbatim[HoldPattern][x_] :> x;

    structName /: (x_structName /; (structValidQ[structName][x])).set[
       property_Symbol, value_] :=
     Catch[getProp[getInstance[x], property,
        Hold[Message[structName::noprop, property];
         Throw[$Failed]]] /. property[y_] :> (y = value)];

    structName /: x_structName.set[property_Symbol, value_] :=
     (Message[structName::badst]; $Failed);

    structName /: (x_structName /; (structValidQ[structName][x])).get[
       property_Symbol] :=
     Catch[First@getProp[getInstance[x], property,
        Hold[Message[structName::noprop, property]; Throw[$Failed]]]];

    structName /: x_structName.get[property_Symbol] :=
     (Message[structName::badst]; $Failed);

    structName /: (x_structName /; (structValidQ[structName][
          x])).delete[] :=
     (
      releaseInstanceStorage[structName][getInstance[x]];
      structInstances[First@x] =.;
      );
    ];
  ];

This is a minimal API to generate structs from the name of the
struct and names of the fields (symbols). It is significantly more complex
than Nasser's suggestion, but it has a number of advantages, in particular
it does not promote the use of global variables (the state is
encapsulated),  and  it allows introspection in the style of Java
reflection, which is often  a pretty powerful tool. Here are some examples:

In[4]:= makeStruct[rectangle,width,height]

In[5]:= obj = rectangle.new[]

Out[5]= rectangle[$3]

In[6]:= obj.get[width]

In[7]:= obj.get[widt]

During evaluation of In[7]:= rectangle::noprop: No property with the name
widt found
Out[7]= $Failed

In[8]:= obj.set[widt,1]

During evaluation of In[8]:= rectangle::noprop: No property with the name
widt found
Out[8]= $Failed

In[9]:= obj.set[width,1]

Out[9]= 1

In[10]:= obj.get[width]

Out[10]= 1

In[11]:= obj2 = rectangle.new[]

Out[11]= rectangle[$6]

In[12]:= obj2.set[height,2]

Out[12]= 2

In[13]:= instances = rectangle.getAllInstances[]

Out[13]= {rectangle[$3],rectangle[$6]}

In[14]:= #.delete[]&/@instances

Out[14]= {Null,Null}

In[15]:= rectangle.getAllInstances[]

Out[15]= {}

In[16]:= destroyStruct[rectangle]

In[17]:= destroyStruct[rectangle]
During evaluation of In[17]:= destroyStruct::nostr: The struct with the name
rectangle does not exist
Out[17]= $Failed

In[18]:= makeStruct[rectangle,width,height]

In[19]:= rectangles = Table[rectangle.new[],{5}]

Out[19]=
{rectangle[$4],rectangle[$8],rectangle[$11],rectangle[$14],rectangle[$17]}

In[20]:= MapThread[#1.set[width,#2]&,{rectangles,Range[5]}]

Out[20]= {1,2,3,4,5}

In[21]:= Map[#.get[width]&,rectangles]

Out[21]= {1,2,3,4,5}

In[22]:= rectangle.getFields[]

Out[22]= {width,height}

In[23]:= destroyStruct[rectangle]

There is some overhead associated with this method. However, if one needs
frequent  in-place modifications, this may be more efficient  than copying
immutable structures and modifying the copy, which is the standard way
most  Mathematica functions work. Some of the expensive type-checks can be
removed to improve speed, if needed.

Regards,
Leonid


On Fri, Oct 15, 2010 at 10:50 AM, Andreas <aagas at ix.netcom.com> wrote:

> Nasser
>
> I've followed this thread for a couple of days and it surprises me
> that it hasn't drawn more discussion.  I think this approach makes
> lots of sense and makes dealing with a wide variety of collections of
> data and results much easier to handle.
>
> I have often found myself "tagging" levels or even elements in a
> output list to try to emulate the same thing, but your idea works much
> better.
>
> I haven't thought this through completely, but do you see this
> approach having any drawbacks?  Might a struct restrict preclude some
> Mapping operations which one could do on a nested list?
>
> I'd like to see this as a package that we could use to define structs
> easily across any notebook.
>
> Very interesting idea.
>
> A.
>
> P.S.  I've posted this through Google Groups but notice that sometimes
> things posted through Google Groups don't seem to appear on the
> original Drexel site.  Anyone else have the trouble?
>
>



  • Prev by Date: Re: How to explain these variations in execution speeds?
  • Next by Date: Re: Simultaneous -> Matrix
  • Previous by thread: Re: something nice I found today, return multiple
  • Next by thread: Overlaying random graphs