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? > >