www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Full Fledged Properties

reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
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
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
So, no one thinks this is a good idea? Any reasons why?
Dec 18 2012
parent reply "Rob T" <rob ucora.com> writes:
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
parent "js.mdnq" <js_adddot+mdng gmail.com> writes:
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