digitalmars.D - Full Fledged Properties
- js.mdnq (140/140) Dec 17 2012 Traditionally properties only have a getter and setter. We can
Traditionally properties only have a getter and setter. We can create full fledged properties in D using the following templates and methodology. Unfortunately, it is somewhat terse. The compiler could do this all for us. The following code, creates a simple class _cChannel, which could represent something about a communication channel. It has a "property" called strength which could represent the signal strength. e.g., class cChannel { double Strength; } But the built in value has little functionality. It may be difficult to use. If we need to convert the value into different formats(dbu, dbV, or whatever), or have methods to compare, they must be somewhere else. Using the templates I came up with, I'm able to create a value wrapper for Strength that allows one to encapsulate all the functionality into a struct called sStrength. sStrength, for all practical purposes, is a double(or whatever). It has a getter and setter, so to speak, but also other functionality. More, so, the whole point of all this, is that a strength value can access it's parent(just as you can do with getters and setters) AND does not use any additional storage than the type it wraps(hence, it is "free", so to speak). The problem? Messy to use. Not pretty. The compiler could clean up all this. If the compiler did this, the code could look like this property pStrength(T) { private: propertyvalue T Strength; // Specifies that Strength is the wrapper for this class. Basically does `alias Strength this` unless we explicitly override it. Not needed. public: void Do::cChannel() { writeln("asdf"); } // Add's Do to pStrength only when it is created inside a cChannel. void Do::cSignal() { writeln("Signal Strength of", Parent.SignalName, " ", Strength); } } class cChannel { pStrength!(double) Strength; } Notice how Strength is a full fledged property with as much or little functionality we want but it only takes up a double even though it can access cChannel's members. The compiler would supply default setter, getter, and casts because it realizes that pStrenght(T) is a wrapper around a T and hence can be converted to and from the specified propertyvalue without issue. We can override this if we want to alter the behavior by explicitly specifiying the opAssigns, alias this, and opCasts. We could also nest properties in classes and structs. If this is done then the Parent defaults to the outer. But we could still specialize the parent in cause the property is used in another class to define a value. e.g., class cChannel { property pStrength(T) { ... void Do() { writeln("asdf"); } // Parent is implicitly a cChannel since nested. (note we changed Do::Channel() to Do()) void Do::cSignal() { writeln("Signal Strength of", Parent.SignalName, " ", Strength); } } pStrength!(double) Strength; } class cSignal { pStrength!(int) Strength; } // Strength's Do will use Do::cSignal Add these to be able to override or accomplish certain tasks in the property above. I left them out to reduce the visual bloating since they are not necessarily needed. //alias opGet this; // Explicitly override getter to supply our own //T opGet() { return Strength; } //alias typeof(this) tthis; //auto opAssign(tthis)(ref tthis sstrength) { this.Strength = sstrength.Strength; return this; } // a custom setter between pStrengths //tthis opAssign()(T Strength) { this.Strength = Strength; return this; } // A custom setter from T // static if (typeof(Parent) == cChannel) { void Do() { writeln("asdf"); } } // Allows us to insert Do when the Parent is cChannel, i.e., it is equivalent to the Do::cChannel syntax. It might even be possible to have a propertyClass which would be a class like property that has inheritance. They would be pretty much identical to a class except have the ability to use the parent specialization notation. It wouldn't offer much benefit as one can already do this sort of thing with classes, unless a propertyClass as a value type rather than a reference type, so it would be in between a struct and a class. It would be more for just expediency since a property could be converted to a propertyClass rather easily if one finds they need inheritance. vs Templates: http://dpaste.dzfl.pl/d8bb92ab class _cChannel(bool _NestLevel = true) { enum string __ClassNameFix = __traits(identifier, typeof(this))~"!("; enum string __NestLevelFix = "_NestLevel"; public: alias double Int; mixin tp_sStrength; mixin(StructNestType!("sStrength!(Int)", "Strength")); // Strength of Channel static if (_NestLevel) { this() { } } } alias _cChannel!() cChannel; static template tp_sStrength() { struct sStrength(T, int _Offset = 0) { private: T Strength; public: alias typeof(this) tthis; mixin(StructNestParent!("Parent")); alias opGet this; T opGet() { return Strength; } void Do() { writeln("asdf"); } static if (_Offset == 0) { // Conversion operators to "orphaned" structs. (This allows orphaned structs to have valid parents) auto opAssign(T)(ref T b) { this.Parent = b.Parent; return this; } this(A)(ref A b) { this.Parent = b.Parent; this.Strength= cast(T)b.Strength; } } else { auto opAssign(tthis)(ref tthis sstrength) { this.Strength = sstrength.Strength; return this; } tthis opAssign()(T Strength) { this.Strength = Strength; return this; } } } }
Dec 17 2012
So, no one thinks this is a good idea? Any reasons why?
Dec 18 2012
On Wednesday, 19 December 2012 at 06:32:41 UTC, js.mdnq wrote:So, no one thinks this is a good idea? Any reasons why?Sorry, but I don't understand your proposal just yet. Considering the subject "Full Fledged Properties", the current concept is very limited in terms of what it is able to do with making a function behave like a variable. Here's an example of a property problem I ran into recently while coding (it is silly because I got stuck on it for a while but shouldn't have). I have a property setter that takes in a struct. The struct has opAssign overridden so that I can assign to it a string type. struct Silly { string _s; void opAssign( string a_val ) { // maybe do some stuff _s = a_val; // maybe do more stuff } } class ReallySilly { Silly _Silly; property void pSilly( Silly a_silly ) { // maybe do some stuff. _Silly = a_silly; // Silly.opSssign is called // maybe do some more stuff. } ref Silly rSilly() { return _Silly; } } auto vReallySilly = new ReallySilly; vReallySilly.pSilly = "string"; // duh, that didn't work! Compiler error. vReallySilly.rSilly = "string"; // this compiles, but is not what I want. Of course a property function is not really a "property", it's just a function not unlike any other. So how can I get Silly.opAssign like behavior to propagate to the setter function without resorting to the ref function? I have to create another function, overloading on the property function. class ReallySilly { ... void Silly pSilly( string a_string ) { // maybe do some stuff Silly = a_string; // maybe do more stuff } } vReallySilly.pSilly = "string"; // Now it works! However, why bother defining a function with property in the first place when there could be multiple setters? Unfortunately D forbids us to overload on the return type, so there can never be more than one getter without resorting to regular distinct named getter functions (I really wish that unnecessary limitation would be removed). --rt
Dec 19 2012
My method is simply defining complex value types with added functionality(methods) that do not cost memory(a ptr) to access their parent. It's really as simple as that. Unfortunately to do that in D requires a lot of boiler plate code. The main key here is "access to parent" as that is the issue at hand. Since complex value types are structs already the issue is of nesting. I've taken care of the problem by doing the computation implicitly(which, unfortunately requires a lot of "metacode" to handle). In your example, if you were only wrapping a string, then you could use my code to do so IF you wanted access to the parent without the cost of an additional pointer. My original post was about codifying a way to deal with such structs(nested structs that have parents with zero waste). In your case, it doesn't seem to apply, again, unless you want access the parent containing your struct which essentially wraps a string. (Although I guess one doesn't need to treat the structs as wrappers of types, that was my original reason) Now, you do this ` property void pSilly( Silly a_silly ) { // maybe do some stuff. _Silly = a_silly; // Silly.opSssign is called // maybe do some more stuff. } ` and if your do stuff does access members in the class, then my method would work for you and solve your problems! You can move all this code into the opAssign of Silly and it will work(by having to use Parent. for members of ReallySeally). So, in some sense you seem to have the same issues that brought me to my solution. You essentially want an opAssign inside ReallySeally for Silly but you can't have that so you use a property. The following code is similar to yours but we move all the getters and setters into Silly rather than having them in ReallySilly. Silly is essentially a parent aware complex value type that doesn't cost to be parent aware. (hence you can do whatever you do in ReallySilly inside Silly instead) (I left out the templates mixin's to get this to work, they can be found in the original dpaste link) import std.stdio; import std.traits; static template tp_Silly() { struct Silly(int _Offset = 0) { string _s; alias _s this; // Simple getter and setter void opAssign( string a_val ) { // maybe do some stuff (can use Parent, might have to specialize on ReallySilly if used with other parents) _s = a_val; // maybe do more stuff } } } class _ReallySilly(bool _NestLevel = true) { enum string __ClassNameFix = __traits(identifier, typeof(this))~"!("; enum string __NestLevelFix = "_NestLevel"; public: mixin tp_Silly; mixin(StructNestType!("Silly", "_Silly")); // i.e., Silly _Silly; } alias _ReallySilly!() ReallySilly; int main(string[] argv) { auto vReallySilly = new ReallySilly; vReallySilly._Silly = "stringggg"; writeln(vReallySilly._Silly); }
Dec 19 2012