Properties: a.b.c = 3

Chad J chadjoan at __spam.is.bad__gmail.com
Fri Jul 31 00:14:56 PDT 2009


Steven Schveighoffer wrote:
> On Wed, 29 Jul 2009 18:27:09 -0400, Chad J
> <chadjoan at __spam.is.bad__gmail.com> wrote:
> 
>>
>> I'd like to know though, what assumptions does this make about the code?
>>  Why might they be incorrect?
> 
> Simply that the compiler assumes it's simply setting a value.  However,
> setters are functions and can execute any code they wish.  You may get
> into weird states that you don't want to or perform unneeded actions
> that you aren't expecting.
> 
> The example given later in this thread is perfect -- setting the width
> and height individually may re-render the widget when you only should
> re-render once.
> 

That's a problem of library design and code architecture, nothing
inherent in properties.  You can make such mistakes using functions as
well.

The mistake here is not so much the multiple calls into the widget, but
the fact that multiple calls would redraw the widget multiple times -
bad!  That thing should wait until the entire screen is redrawn.

There are data types where changes to state are expensive.  They tend to
be designed with the ability to queue up changes and execute them all at
once.  Whether the changes are done with functions or properties would
not matter.

> Another example is something like this:
> 
> class C
> {
>   int x;
> }
> struct S
> {
>   C c;
> }
> 
> struct S2
> {
>   C c;
>   S s() { return S(c); }
>   void s(S s) {c = s.c;}
> }
> 
> void main()
> {
>   S2 s2;
>   s2.c = new C;
>   s2.s.c.x = 5;
> }
> 
> If the compiler rewrites that last line as:
> 
> auto tmp = s2.s;
> s2.c.x = 5;
> s2.s = tmp;
> 

I think there's a typo.

auto tmp = s2.s;
tmp.c.x = 5;
s2.s = tmp;

I'm going to assume that's what you meant.

> Then it is completely unnecessary, and could cause problems if the
> setter does something else besides setting.
> 

I'm not quite sure I follow.

I'm reading the first bit as, "if I write code just like so, then
's2.s.c.x = 5;' will generate extra code because 's2.s.c.x = 5;' works
fine without the rewrite."  A true statement.

I don't see how it relates to setters doing other things.  Setters don't
have to be written like that, and they can do other perfectly useful
things that won't cause any problems.

The setter can execute arbitrary code.  Of course it can cause problems!
 Programmers aren't going to make it cause problems though.

>> If the compiler /knows/ that something being used is a property, I just
>> don't understand how that information is incomplete.
> 
> First, because the problem of determining what code does is an
> NP-complete problem -- the compiler can't know what code is going to do
> except by running it.  It's just like trying to write a program that
> detects infinite loops.  So it must make some assumptions.
> 
> Second, because D allows opaque function declarations, so the compiler
> might not even be able to *look* at what a property does.  It can only
> assume, and the assumption you would have it make is that a property is
> a simple setter that has no other side effects.
> 

I never suggested analyzing the code.  The implication is that the
definition contains information telling the compiler that it is a
property getter/setter.

Assuming the properties don't have side effects is also a bad idea.
Side effects are one of the big uses of properties.  They allow you to
set dirty-flags, increment/decrement counters, lazily allocate memory,
call events, and do any number of nifty things.  Functions let you do
those nifty things too, but if you have an existing API that has fields,
you can't use functions without breaking API changes.  However, you can
migrate the fields to properties without breaking user code and then
reap the benefits of functions without using functions.  Not to mention
the bonus in being able to make the API's intent more clear (that whole
verb vs noun thing).

Like many features, properties can be used to do bad things.  The
important thing is that the programmer must be very intentional to
accomplish said bad things.  Using properties correctly is very easy and
natural.  (Note that I don't consider D's current properties to actually
be properties.  Omissible parentheses is a better name.)

In the Widget/Rectangle example, properties can be used to set dirty
flags whenever the rectangle is modified.  Now the GUI library knows
exactly which Widgets have changed and which haven't, and all without
having to diff against previous state.  This information can be used to
only modify the pixels over places where widgets changed.  This example
is somewhat fictitious though, since that optimization doesn't bring you
many gains with today's GPUs.  Still, it does hint at the usefulness of
monitoring changes in state.  Currently the only way to do this in a
complete and *future-proof* manner is to use interfaces that expose only
functions.  Some people seem perfectly happy with that, but I dislike
how it tends to cause code bloat (SLoC mostly) and terrible violations
of DRY.  The lack of nouns is a little annoying too.

>> Also macros aren't a good fit for this because they aren't going to
>> exist in D2, or necessarily ever.  Well, properties may not pop up in D2
>> either, but if they're going to happen it'd be nicer if they did.
> 
> I think you can solve this problem (and solve it correctly) with some
> sort of fancy template magic, but not without some extra baggage (such
> as an extra type that might not be optimized).  I envision something
> like this:
> 
> with(Setter!(widget.location)) // don't know if this works
> {
>    width = 50;
>    height = 100;
>    // at scope end, Setter sets widget.location to it's internal
> representation via a finalizer
> }
> 

That seems to solve a different problem.  That might actually be an
interesting trick.  I'll have to try and use it some time.

Properties are often used for doing bookkeeping on the callee's side, so
requiring the caller to do a bunch of bookkeeping defeats the purpose.

> The reason I said macros is because they have the power to rewrite the
> code exactly like you said the compiler should.  At least with a macro
> you would have some indication that it is exactly what the user wants,
> and you can have the power to do your optimizations.
> 
> -Steve

My issue with macros isn't a lack of capability, but rather a lack of
existence ;)



More information about the Digitalmars-d mailing list