www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Suggestion: properties should be treated as 'virtual members variables'

reply Kristian <kjkilpi gmail.com> writes:
In digitalmars.D.announce there has been discussion about why you cannot=
  =

use properties as data members. For example, why the following (simple) =
 =

example doesn't work:

class A {
     int prop() {return m_val;}
     int prop(int val) {return m_val =3D val;}

     int m_val =3D 0;
}

void f(inout int x) {
     x++;
}

void func() {
     A a =3D new A;

     f(a.prop);  //error: ((a).prop)() is not an lvalue
}


I think properties should work just like they were data members:  =

everywhere you could give a data member you can also give a property. So=
,  =

for example, the following should also work:

a.prob++;
a.prob -=3D 10;

Hence the implementation of 'prob' will be hidden from the user.

I would very much see properies as a 'virtual member variables'.


Hmm, would it be simplier to implement properties by using virtual table=
s  =

for variables?...

(What object oriented languages did for functions, D does it for  =

variables! *grin*)
Sep 05 2006
next sibling parent reply Reiner Pope <reiner.pope REMOVE.THIS.gmail.com> writes:
While we're discussing this, I'm wondering: what is the reason that D 


Somewhere, the spec says that properties are useful for turning a pure 

properties allow this feature, including the required syntax for 
treating them as lvalues, and I believe this is pretty safe because of 
the explicit nature of declaring properties.

Walter, is there a reason you decided against such a syntax, or are you 
simply not aware of it?

As to the solution for D, I like what Derek proposed:

a.func();

means func is treated as a function, with no special semantics (which 
means it can't be an lval), but

a.func;

means that func is treated as a property. This means a getter is 
required for rval semantics and a setter is required for lval semantics.

However, the problem with this is that it does not obviously scale. For 
instance, how does this approach work with opIndex(Assign) where there 
are multiple parameters? Should the solution to this only work for 
opIndex, or should it apply to arbitrary functions?

I haven't given this too much thought, but my current inclinations is 
that an explicit syntax is the right way to manage this. This suggestion 

parameters:

1. Add the keyword 'property', 'value', 'get' and 'set'. Alternatively, 
you could use -> and <- instead of get and set if the keywords are 
already used too much.

2. Syntax looks like this:

class C
{
	private int m_val;

a nested function. This is the simplest type of property.
	property int Val
	{
		get { return m_val; }
		set { m_val = value; }
	}
}

This would get expanded to

class C
{
	private int m_val;
	int __set_Val(int value) { return m_val = value; }
	int __get_Val() { return m_val; }
}


properties:


class D
{
	private int[] myArray;
	property int myAccessor(int index)
	{
		get { return myArray[index]; }
		set { myArray[index] = value; }
	}
}

Being explicit like this lets you pair up arbitrary functions into 
getters and setters to behave like this. To ensure against unexpected 
behaviour, then you keep Derek's rule:

a.foo; // requires foo is a property or data member
a.foo(); // requires foo is a function

Thanks for following this disjointed post.

Cheers,

ReinerHmm, would it be simplier to implement properties by using virtual 
tables for variables?...

(What object oriented languages did for functions, D does it for 
variables! *grin*)
Sep 05 2006
parent reply mike <vertex gmx.at> writes:
Hi!

I hope you don't mind if I throw in some ideas on that.

 2. Syntax looks like this:

 class C
 {
 	private int m_val;

om =
 a nested function. This is the simplest type of property.
 	property int Val
 	{
 		get { return m_val; }
 		set { m_val =3D value; }
 	}
 }
(1) Get rid of superflous {} characters when there's only one statement.= ' property int Val ' { ' get return m_val; ' set ' { ' assert(value < 20); ' m_val =3D value; ' } ' } (2) Introduce new "operators". Maybe use operator notation? ' property int Val ' { ' get return m_val; ' set ' { ' assert(value < 20); ' m_val =3D value; ' } ' increase ' { ' assert(value < 19); ' return m_val++; ' } ' decrease ' { ' assert(value > 0); ' return m_val++; ' } ' } (3) Finally - maybe use opXX() notation so there won't be lots of new = keywords and with the () it fits more nicely into the syntax. ' property int Val ' { ' opReturn() return m_val; ' opAssign(int value) ' { ' assert(value < 20); ' m_val =3D value; ' } ' opIncrement() ' { ' assert(value < 19); ' return m_val++; ' } ' opDecrement() ' { ' assert(value > 0); ' return m_val++; ' } ' } I like (3) very much. -Mike -- = Erstellt mit Operas revolution=E4rem E-Mail-Modul: http://www.opera.com/= mail/
Sep 05 2006
next sibling parent Kristian <kjkilpi gmail.com> writes:
On Tue, 05 Sep 2006 17:10:59 +0300, mike <vertex gmx.at> wrote:
 Hi!

 I hope you don't mind if I throw in some ideas on that.

 2. Syntax looks like this:

 class C
 {
 	private int m_val;

 from a nested function. This is the simplest type of property.
 	property int Val
 	{
 		get { return m_val; }
 		set { m_val =3D value; }
 	}
 }
(1) Get rid of superflous {} characters when there's only one statemen=
t.
 ' property int Val
 ' {
 '      get return m_val;
 '      set
 '      {
 '          assert(value < 20);
 '          m_val =3D value;
 '      }
 ' }

 (2) Introduce new "operators". Maybe use operator notation?

 ' property int Val
 ' {
 '      get return m_val;
 '      set
 '      {
 '          assert(value < 20);
 '          m_val =3D value;
 '      }
 '      increase
 '      {
 '          assert(value < 19);
 '          return m_val++;
 '      }
 '      decrease
 '      {
 '          assert(value > 0);
 '          return m_val++;
 '      }
 ' }

 (3) Finally - maybe use opXX() notation so there won't be lots of new =
=
 keywords and with the () it fits more nicely into the syntax.

 ' property int Val
 ' {
 '      opReturn() return m_val;
 '      opAssign(int value)
 '      {
 '          assert(value < 20);
 '          m_val =3D value;
 '      }
 '      opIncrement()
 '      {
 '          assert(value < 19);
 '          return m_val++;
 '      }
 '      opDecrement()
 '      {
 '          assert(value > 0);
 '          return m_val++;
 '      }
 ' }

 I like (3) very much.

 -Mike
The syntax of (3) is nice if properties can have multiple operators. (There is very close relation between a property and a class. A property= = has only operators and it's logically treated as a data member of its = type.) But, because you cannot overload operators of basic types, how should th= e = following work: void f(inout int v) { v++; } f(obj.Val); The 'opIncrement' operator (or 'opAdd()') of 'Val' cannot be called insi= de = 'f()'. The answer to this is that the compiler does the following (as wa= s = described in an earlier message): auto x =3D obj.Val; f(x); obj.Val =3D x; This means that everything should be doable with the read and write = operators only. Hence I think that there would not be need for the other= = operators. So (1) looks very nice.
Sep 05 2006
prev sibling parent Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Tue, 05 Sep 2006 22:21:39 +1000, Reiner Pope
<reiner.pope REMOVE.THIS.gmail.com> wrote:

While we're discussing this, I'm wondering: what is the reason that D 

...
Walter, is there a reason you decided against such a syntax, or are you 
simply not aware of it?
If I can throw in a guess, it is sometimes kind of nice that a property can be treated as a method. For instance, if you need a delegate for a getter/setter function to pass to another function, you don't need to write it again - you just use the existing property. That said, I'm not sure if the trade-off works. Explicit properties C++Builder, Visual C++ (even without .NET), Python. They work well enough. I've not had the experience with the D method to form a clear opinion yet, but I have my doubts. On Tue, 05 Sep 2006 16:10:59 +0200, mike <vertex gmx.at> wrote:


(1) Get rid of superflous {} characters when there's only one statement.

' property int Val
' {
'      get return m_val;
'      set
'      {
'          assert(value < 20);
'          m_val = value;
'      }
' }
One advantage of the D method is just a little bit more flexibility. With setters, you can decide whether to return the assigned value or not. That means you can decide whether to support the following... var1 = obj.propertyname = var2; Don't know how important that choice is in D yet, but certainly in C++ I don't always return anything from a custom assignment operator. -- Remove 'wants' and 'nospam' from e-mail.
Sep 06 2006
prev sibling next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Kristian" <kjkilpi gmail.com> wrote in message 
news:op.tfelx1zc5xlco7 mist...

 I think properties should work just like they were data members: 
 everywhere you could give a data member you can also give a property.
I totally agree. In my scripting language (or rather, in a previous class Foo { def int mX; property int x { set(int value) { return mX = value; } get { return mX; } } } .. def Foo f = new Foo(); f.x = 4; writefln(f.x); f.x += 6; // == f.x.set(f.x.get() + 6) writefln(f.x); Then I got even more general, and came up with namespaces + operator overloading: class Foo { def int function(int)[] mCallbacks; namespace callbacks { def int function(int)[] opGet() { return mCallbacks.dup; } def void opCatEq(int function(int) func) { mCallbacks ~= func; } def int function(int) opIndex(int index) { return mCallbacks[index]; } def int length() { return mCallbacks.length; } } } ... def Foo f = new Foo(); f.callbacks ~= someFunc; f.callbacks ~= anotherFunc; for(def int i = 0; i < f.callbacks.length(); ++i) writefln("callback ", i, ": ", f.callbacks[i]); def int function(int)[] cb = f.callbacks; The advantage to this is that you can make really complex and abstract "data structures" out of what really amounts to syntactic sugar. And unlike nested structs or classes, these members can be overridden, since the namespace only adds a piece to the name instead of creating an entirely new data structure. And namespaces can live anywhere, and not just in classes... OK, I'm going off on a bit of a tangent here, and it's doubtful Walter would go this far. We can dream, though :) But I think the point everyone is trying to make with wanting explicit property syntax is that if the compiler _knows_ about properties, it can do cool things with them. I mean, Walter echoes this sentiment here: http://www.digitalmars.com/d/builtin.html . Right now, the compiler doesn't know anything about properties; they're nothing more than a hackish way to write a function call.
Sep 05 2006
parent Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Tue, 5 Sep 2006 09:52:44 -0400, "Jarrett Billingsley"
<kb3ctd2 yahoo.com> wrote:

Then I got even more general, and came up with namespaces + operator 
overloading:

class Foo
{
    def int function(int)[] mCallbacks;

    namespace callbacks
    {
        def int function(int)[] opGet()
        {
            return mCallbacks.dup;
        }
...
    }
}
I like it. It reminds me of something I did with properties in Python, even though it was a lot of hassle. I wanted reverse iteration. I solved it by having a 'reverse' propery which returned a proxy object. The proxy referred back to the original, but implemented some methods 'backwards'. Request an iterator and you get one, but set up to generate the values in reverse order. I had a class set up to represent 'the set of integers', so that I could use slicing and reversing to specify a range of values for a for loop such as... for i in Integers.reverse [0:10] : The point being that writing a reversed slice (with a half-open range) is just slightly painful - reversing the above slice gives [9:-1:-1] for instance. It was proof of concept rather than real code, but I liked the result. But I didn't really want a proxy. I just wanted an easy notation. Your 'namespace' syntax could have given me the same notation, and avoided the proxy object overhead. Another thought - overloaded constructors by name. There are sometimes different ways to construct an object for different purposes. They can't always be separated by signature, and even if they can, the signature doesn't always express the purpose. Usual solution - only partly construct the object, and use ordinary methods to finish the initialisation. Putting a constructor in one of your namespaces would, it seems, do the trick, allowing... c_Object l_Object = new c_Object.alt_constructor_name (blah); I seem to remember Turbo Pascal allowing any method to be declared as a constructor for similar reasons, though that was a long long time ago and I've never used Delphi. I assume the special-constructor-name systems main benefit is that there's an obvious default constructor, but it would still be nice to have named alternatives at times. -- Remove 'wants' and 'nospam' from e-mail.
Sep 08 2006
prev sibling next sibling parent reply Marcio <mqmnews123 sglebs.com> writes:
Kristian wrote:
 
 In digitalmars.D.announce there has been discussion about why you cannot 
 use properties as data members.
Personally I like the Eiffel approach of not requiring () when calling a method that does not need parameters. This allows the implementer of the class to switch between a plain slot or real code that gets run to compute&return, without impacting clients. No modification is needed at the source code level at client classes. Users just say a.foo and the foo implementer can choose the best implementation approach. No complicated notation to define it "as a property" etc. It is true that you can't assign to it to change it (a.foo := 3), you still need the method call notation (something that properties do for you, that extra syntax sugar). Some people like it better this way, some people prefer being able to assign (conceptually). marcio
Sep 05 2006
next sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Marcio" <mqmnews123 sglebs.com> wrote in message 
news:edka8t$2e0j$1 digitaldaemon.com...

 Personally I like the Eiffel approach of not requiring () when calling a 
 method that does not need parameters. This allows the implementer of the 
 class to switch between a plain slot or real code that gets run to 
 compute&return, without impacting clients. No modification is needed at 
 the source code level at client classes. Users just say a.foo and the foo 
 implementer can choose the best implementation approach. No complicated 
 notation to define it "as a property" etc.
But this _is_ how D does it right now, i.e. class Foo { int bar() { return 4; } } Foo f = new Foo(); writefln(f.bar); Or even: int foo() { return 5; } writefln(foo); We're not happy with this, because of the expressivity that is lost when using things like op=.
Sep 05 2006
prev sibling parent reply Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Tue, 05 Sep 2006 12:57:05 -0400, Marcio <mqmnews123 sglebs.com>
wrote:

	Personally I like the Eiffel approach of not requiring () when calling 
a method that does not need parameters.
Not just Eiffel. D too. I seem to remember Pascal doing the same, which might be how the idea of properties arose, though I'm really guessing now. I've just realised that I have completely misunderstood the problem myself. But I haven't formed a new understanding. I'm confused. So, here is my previous understanding and it's fundamental error - please can someone explain where I'm going wrong... The starting point is that, in order to handle the example, two propery calls are needed... : void f(inout int x) { : x++; : } That's an inout function, so... : void func() { : A a = new A; : : f(a.prop); //error: ((a).prop)() is not an lvalue : } The compiler calls the getter and gets a value for the property, but that result isn't in a variable (an 'lvalue' is normally a variable - something you can assign into). Without a variable, you can't do a pass-by-reference for the inout parameter. Similar problems happen with assignment operators like '++' and '+='. Compiler level fix - the compiler creates a temporary. The code is translated to... temp = a.prop (); f (temp); a.prop (temp); If D doesn't do that fix, the question is why not? Well, that's what I'm not sure about. My first thought was that handling the property seems to be a two stage process during semantic analysis. Evaluating 'a.prop' seems ambiguous. It might be intended to give a delegate, or it might be intended to call a property method. An easy fix is to always give a delegate, but to allow that delegate to convert to the type of the property using an implicit call. But, at that second stage, when the delegate is called, the compiler is dealing with a delegate - not a member function or property. For a delegate, the compiler doesn't even know what class it relates to - let alone if there is a setter to match the getter. With explicit properties, this wouldn't be necessary - there would be no need to start with an initial delegate since the compiler knows from the start that it's intended as a property. OK - but, in principle, the compiler can still handle this without needing explicit property declarations. All it needs is a more sophisticated semantic analysis. Instead of creating a 'delegate' semantic representation, the compiler should create an 'any-one-of-these-things' semantic representation and then use the context resolve it. And this is where I'm confused about all this. D must already be doing some of this. After all, take another look... : class A { : int prop() {return m_val;} : int prop(int val) {return m_val = val;} : : int m_val = 0; : } There is a getter _and_ a setter, so when you write 'a.prop' the compiler cannot immediately know which method to use for the delegate. The same happens any time you create a delegate from an overloaded method, not just for properties. The compiler needs to create a generalised 'one-of-these-thingies' semantic object, then resolve it from context later. So why not handle inout parameters? What am I missing? Or is it just a 'not done yet' kind of thing? -- Remove 'wants' and 'nospam' from e-mail.
Sep 08 2006
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Steve Horne" <stephenwantshornenospam100 aol.com> wrote in message 
news:7i52g25vn3qkg41uhoako5jmr5135jif0u 4ax.com...

 D must already be doing some of this. After all, take another look...

 : class A {
 :      int prop() {return m_val;}
 :      int prop(int val) {return m_val = val;}
 :
 :      int m_val = 0;
 : }

 There is a getter _and_ a setter, so when you write 'a.prop' the
 compiler cannot immediately know which method to use for the delegate.
 The same happens any time you create a delegate from an overloaded
 method, not just for properties. The compiler needs to create a
 generalised 'one-of-these-thingies' semantic object, then resolve it
 from context later.

 So why not handle inout parameters?

 What am I missing?
I thought about it, and it would require the method that takes the inout param to have different implementations. All an inout parameter really is is an implicit address-of. So when you write: void foo(inout int x) { x++; } int x = 5; foo(x); It becomes: void foo(int* x) { (*x)++; } int x = 5; foo(&x); When you try to pass a property to it, what happens? Properties are methods, not variables. They have to be called, they can't just be dereferenced. The inout-taking function can't know whether it's being passed a method or a variable, and it has to do different things for both. It just can't work.
Sep 08 2006
parent Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Fri, 8 Sep 2006 10:15:45 -0400, "Jarrett Billingsley"
<kb3ctd2 yahoo.com> wrote:

When you try to pass a property to it, what happens?  Properties are 
methods, not variables.  They have to be called, they can't just be 
dereferenced.
Yeah, I know. I guess I'm just used to C++ creating temporaries whenever it feels like it. I guess the problem with that is that the temporaries aren't free, and aren't always cheap. So better to think extra hard before adding a compiler feature that people will get dependent on. It still feels wrong, though, to have properties that can't always do the job of the data fields they are meant to replace. Still much better than not having properties, though. -- Remove 'wants' and 'nospam' from e-mail.
Sep 09 2006
prev sibling parent Chad J <gamerChad _spamIsBad_gmail.com> writes:
Just thought I'd point out another shortcoming of the current approach 
to properties.
Suppose you have the following code:

class Foo
{
   void delegate() bar;

   void func()
   {
     writefln( "example" );
   }

   this()
   {
     bar = &func;
   }
}

void main()
{
   Foo foo = new Foo();
   foo.bar();
}

This prints "example".  But now the class is rewritten so that the 
delegate is hidden behind a property:

class Foo
{
   void delegate() m_bar;
   void delegate() bar() { return m_bar; }

   void func()
   {
     writefln( "example" );
   }

   this()
   {
     m_bar = &func;
   }
}

void main()
{
   Foo foo = new Foo();
   foo.bar();
}

Unfortunately this still compiles and runs...  incorrectly.  It prints 
nothing.  foo.bar() just returns a delegate, but doesn't call it.


you.  Currently it's just shorthand for function calling, and that 
hardly helps me.  I don't mind typing a few extra characters to call a 
function.  What I want is to be able to treat properties as data members 
in /every/ way on the calling side.

I hope there are plans to change the current state of properties at some 
point, regardless of backwards compatibility.

Kristian wrote:
 
 In digitalmars.D.announce there has been discussion about why you 
 cannot  use properties as data members. For example, why the following 
 (simple)  example doesn't work:
 
 class A {
     int prop() {return m_val;}
     int prop(int val) {return m_val = val;}
 
     int m_val = 0;
 }
 
 void f(inout int x) {
     x++;
 }
 
 void func() {
     A a = new A;
 
     f(a.prop);  //error: ((a).prop)() is not an lvalue
 }
 
 
 I think properties should work just like they were data members:  
 everywhere you could give a data member you can also give a property. 
 So,  for example, the following should also work:
 
 a.prob++;
 a.prob -= 10;
 
 Hence the implementation of 'prob' will be hidden from the user.
 
 I would very much see properies as a 'virtual member variables'.
 
 
 Hmm, would it be simplier to implement properties by using virtual 
 tables  for variables?...
 
 (What object oriented languages did for functions, D does it for  
 variables! *grin*)
Sep 08 2006