DIP4

From D Wiki
Jump to: navigation, search
Title: Properties
DIP: 4
Version: 2
Status: Superseded
Created: 2009-07-24
Last Modified: 2009-07-29
Author: Nick Sabalausky
Links: DIP4/ArchiveNG discussionAnother discussionBug caused by current designOriginal Prowiki Page
Note: Properties are being officially added to D2.x as part of Annotations. This page should be updated to reflect the actual syntax used for the property annotation. This page has already been archived and the version increased.

Abstract

An alternate usage/definition syntax for properties.

Definition of Terms

The concepts of property and method need to be stated to maintain focus on the issue.

property

An attribute of a object. Equivalent to 'noun' or 'adjective'. An object has properties. One can assign value to a property and one retrieve the current value of a property. One cannot invoke a property.

method

A behavior of an object. Equivalent to 'verb'. An object does methods. One can invoke a method in order to manifest some capability of an object. One cannot assign a value to a behavior.

Any implementation of properties should be consistent with these conceptual views of the terms, such that a reader of D code that is using properties can see which members of a class are attributes and which are behaviors.

Note: Member variables are a subset of properties. They are the set of properties whose value is explicitly stored in a single declaration of RAM belonging to the object. Other properties might not have their value stored, but is instead derived at time of access based on the values of any number of other properties.

Rationale

D's current property syntax has a number of problems:

  • It is not possible for the class designer to prevent nonsensical uses, such as:
// Looks like an assignment, but isn't.
// Looks like 'writefln' is an attribute of the std.stdio module.
writefln = 5;

// An assignment, but is unclear.
// Looks like 'width' is a behavior of the 'widget' object.
widget.width(5);

class Foo
{
    // private data here

    void mutate(bool optionA=true)
    {
        // Modify foo in-place.
        // "optionA" is some algorithm-adjusting option.
    }
}
auto f = new Foo();
// Looks like you are setting the value of Foo's 'mutate' attribute.
f.mutate = false; // Extremely unclear and misleading.
  • Allows ambiguous-looking code:
int delegate() foo() {
        return delegate int(){ return 5; };
}

// Is this an int or a delegate?
auto x = foo();

// Is this a reference to foo, or a reference to the delegate returned by foo?
auto y = &foo
  • Cannot use +=, -=, etc.
  • IDE's, debuggers and doc-generators (as well as programmers) cannot easily identify properties as properites and treat them accordingly (such as by including them in automatically-populated watch tables).
  • It is somewhat non-DRY in many cases, needing to duplicate the type and name (or some variant of the name) numerous times:
// Not an unreasonable example of current property definition.
// Can be improved somewhat, but mostly just with
// metaprogramming/mixins, and that's too ugly for such a common
// construct and it doesn't solve the other issues anyway.

private int _width;              // type: 1, name: 1
int width()                      // type: 2, name: 2
{
        return _width;           // type: 2, name: 3
}
int width(int value)             // type: 4, name: 4
{
        return (_width = value); // type: 4, name: 5
}

As to the fact that in order to call parameterless function one can omit braces and this saves typing efforts, making code readable for humans is more important than making it easier to write.

Usage

  class Foo
{
    // Using full syntax
    int width = int.init
    {
        get { return value; }
        set { value = set; }
    }

    // Identical to width, but using syntactic sugar.
    int height { get; set; }

    // Call setter/getter from within setter/getter, by going through 'this'
    int chattyValue
    {
        get
        {
            writefln("Hello");
            return value;
        }
        set
        {
            value = set;
            
            // Go through 'this' (implicitly in this case) to call getter.
            auto dummy = chattyValue;
        }
    }
    
    // Implicit 'value' need not be used and can be optimized away
    char[] dbProp
    {
        get
        {
            return getFirstRowFromDB("SELECT myText FROM myTable WHERE id=0");
        }
        set
        {
            dbExec("UPDATE myTable WHERE id=0 SET myText='"~escape(set)~"'");
        }
    }

    int readOnly { get; }
    char[] writeOnly ( set; ) // Rare, but allowed
    
    void changeReadOnly(bool makeItBig)
    {
        readOnly = makeItBig? 9999 : 1;
    }
    
    void displayWriteOnly()
    {
        writefln(writeOnly);
    }
    
    int getFive()
    {
        return 5;
    }

    int delegate() generatorDg { get; set; }

    // For illustrative purposes:
    int delegate() getGeneratorDg() { return generatorDg; }
}

unittest
{
    auto f = new Foo();
    
    // Foo.width
    assert(f.width == int.init);
    f.width = 20;
    f.width(20); // ERROR! An int is not callable!
    assert(f.width == 20);
    f.width += 5; // Calls getter, saves it in a temp, does += on temp, calls setter with temp.
    f.width++; // Works similarly 
    assert(f.width == 26);
    auto widthRef = &f.width; // A "struct Property!(int)" of some sort exposing get/set as delegates
    
    // Foo.chattyValue
    assert(f.chattyValue == int.init); // Displays "Hello"
    f.chattyValue = 20;                // Displays "Hello"
    assert(f.chattyValue == 20);       // Displays "Hello"

    // Foo.dbProp
    f.dbProp = "hello";
    assert(f.dbProp == "hello");
    
    // Foo.readOnly
    assert(f.readOnly == int.init);
    f.changeReadOnly = true; // ERROR! Not a variable or property!
    f.changeReadOnly(true);
    assert(f.readOnly == 9999);
    f.readOnly = 5; // ERROR! Writing to this prop is private-only.
    auto roRef = &r.readOnly; // A "struct Property!(int)" of some sort exposing just get
    
    // Foo.writeOnly
    assert(f.writeOnly == int.init); // ERROR! Reading this prop is private-only.
    f.writeOnly = "Whee";
    f.displayWriteOnly(); // Displays "Whee"

    // Misc
    writefln = "fizzle"; // ERROR! writefln is a function, not an lvalue!
    auto five = f.getFive; // ERROR! Not a variable or property!
    auto five = f.getFive();
    
    // Foo.generatorDg
    auto myDg = int delegate() { return 42; }
    f.generatorDg = myDg;
    
    auto g = &f.generatorDg;    // A "struct Property!(int delegate())" of some sort exposing get/set
    auto g = f.generatorDg;     // g == myDg
    auto g = f.generatorDg();   // g == 42
    auto g = f.generatorDg()(); // ERROR! An int is not callable!
    
    auto g = &f.getGeneratorDg;    // g is a delegate that returns myDg
    auto g = f.getGeneratorDg;     // ERROR! Not a variable or property!
    auto g = f.getGeneratorDg();   // g == myDg
    auto g = f.getGeneratorDg()(); // g == 42
}

Description

Much of it should be self-explanatory from the example above, but here are additional notes:

  • Properties are defined just like variables, except ending with {...} instead of ;. This keeps the definition syntax as consistent as possible while still being easy for the parser to distinguish from variable declarations.
  • Each property automatically has it's own private storage named value which eliminates the need for a redundant declaration with arbitrarily-chosen naming conventions and allows the property itself to be easily renamed without any change to the get/set code (much like constructors being named this). This value is never accessed directly from outside the setter/getter (except as described below), and it always means exactly the same thing, so there's no need to be able to name it manually (unlike, for instance, function parameters).
  • Direct access to the implicit underlying private value is typically limited to within the property's definition, so it should be easy to detect whether it can safely be optimized away. For cases where it is necessary for this value to be accessed directly by other members of the class (which should only ever be needed for optimization purposes), that access can be obtained through traits (NEED HELP - actual syntax/semantics of this). But trying to do so if it's been optimized away (ie, never accessed within the property's get/set) is a (again, fairly easy-to-detect) error because if the getter/setter aren't accessing value, it doesn't make sense for anything else to either.
  • Currently, taking the address of a property results in a delegate. Under this proposal, it results in a templated struct containing two delegates, a setter and a getter. (NEED HELP) This should have some way of handling read-only and write-only properties, maybe through some hierarchy of structs/interfaces or fancy templating, or setting the inaccessible ones to null?
  • (NEED HELP) How to handle whether or not subclasses can write to read-only props, get at value through traits, etc? Use optional access qualifiers on the get and set? What are the defaults?
  • The tokens get and set are not keywords, they just have this special meaning inside a property definition. But, they lose that special meaning again within the actual set/get bodies (except as described below).
  • Within the setter, set is used to refer to the new value. So a default setter would be set {value = set;}. This has much the same reasoning as the implicit value: It always means the same thing (unlike the parameters of normal functions), so it's better to standardize it than to require extra verbosity just for the sake of what essentially amounts to arbitrary personal customization. (And if you *really* want to change it you can always do set {auto myFancyName=set; ...}.)
  • TODO: define inheritance semantic.

Comments

Please use the 'digitalmars.D' newsgroup for comments and discussion:

The existing comments have been moved there.

Copyright

This document has been placed in the Public Domain.