Difference between revisions of "DIP23"

From D Wiki
Jump to: navigation, search
(Talk)
(unittest)
Line 298: Line 298:
  
 
<syntaxhighlight lang=D>
 
<syntaxhighlight lang=D>
 +
// Could be any type
 
alias Type = int;
 
alias Type = int;
  
Line 308: Line 309:
 
       @property ref Type baz();  // ref return getter == auxiliary setter
 
       @property ref Type baz();  // ref return getter == auxiliary setter
 
   }
 
   }
 +
 
   S s;
 
   S s;
 +
  // Correct, normal property read
 
   static assert( __traits(compiles, { s.foo;    }));
 
   static assert( __traits(compiles, { s.foo;    }));
 +
  // Cannot apply "()" explicitly to a property
 
   static assert(!__traits(compiles, { s.foo();  }));
 
   static assert(!__traits(compiles, { s.foo();  }));
 +
  // s.foo automatically applies the property
 
   static assert(is(typeof(s.foo) == Type));
 
   static assert(is(typeof(s.foo) == Type));
 +
  // Taking the address reveals the delegate
 
   static assert(is(typeof(&s.foo) == Type delegate()));
 
   static assert(is(typeof(&s.foo) == Type delegate()));
  
 +
  // Correct, normal property write
 
   static assert( __traits(compiles, { s.bar = 1; }));
 
   static assert( __traits(compiles, { s.bar = 1; }));
 +
  // Cannot write properties with the function call syntax
 
   static assert(!__traits(compiles, { s.bar(1);  }));
 
   static assert(!__traits(compiles, { s.bar(1);  }));
 +
  // A write-only property does not make sense without the assignment
 
   static assert(is(typeof(s.bar)) == false);
 
   static assert(is(typeof(s.bar)) == false);
 +
  // Taking the address reveals the delegate
 
   static assert(is(typeof(&s.bar) == void delegate(Type)));
 
   static assert(is(typeof(&s.bar) == void delegate(Type)));
  
 +
  // Correct, normal property read
 
   static assert( __traits(compiles, { s.baz;    }));
 
   static assert( __traits(compiles, { s.baz;    }));
 +
  // Cannot use "()" with properties
 
   static assert(!__traits(compiles, { s.baz();  }));
 
   static assert(!__traits(compiles, { s.baz();  }));
 +
  // The property is read, writing is done through the resulting ref
 
   static assert( __traits(compiles, { s.baz = 1; }));
 
   static assert( __traits(compiles, { s.baz = 1; }));
 +
  // Automatically apply "()"
 
   static assert(is(typeof(s.baz) == Type));
 
   static assert(is(typeof(s.baz) == Type));
 +
  // Taking the address reveals the delegate
 
   static assert(is(typeof(&s.foo) == ref Type delegate()));
 
   static assert(is(typeof(&s.foo) == ref Type delegate()));
 +
  // Changing precedence with parens reveals the returned type
 +
  static assert(is(typeof(&(s.foo)) == Type*));
 
}
 
}
 +
 
unittest
 
unittest
 
{
 
{
Line 334: Line 352:
 
   }
 
   }
 
   S s;
 
   S s;
 +
 +
  // @@@ Review from here
 
   static assert( __traits(compiles, { s.foo;    }));
 
   static assert( __traits(compiles, { s.foo;    }));
 
   static assert( __traits(compiles, { s.foo();  }));
 
   static assert( __traits(compiles, { s.foo();  }));

Revision as of 19:07, 3 February 2013

DIP23: Fixing properties redux

Title: Fixing properties
DIP: 23
Version: 1
Status: Draft
Created: 2013-02-02
Last Modified: 2013-02-02
Author: Andrei Alexandrescu and Walter Bright
Links:

Abstract

There has been significant debate about finalizing property implementation. This document attempts to provide a proposal of reasonable complexity along with checkable examples.

Forces:

  • Break as little code as possible
  • Avoid departing from the existing and intended syntax and semantics of properties
  • Make economy of means (little or no new syntax to learn)
  • Avoid embarrassing situations such as expressions with unexpressible types or no-op address-of operator (as is the case with C functions).

Description

The -property switch gets deprecated

This DIP obviates any behavioral change via -property.

Optional parens stay in

One can't discuss properties without also discussing optional parens. These obviate to some extent the need for properties (at least of the read-only kind) and make for potential ambiguities.

This proposal sustains that optional parentheses should stay in. That means, if a function or method may be called without arguments, the trailing parens may be omitted.

unittest
{
    int a;
    void fun1() { ++a; }
    // will call fun
    fun1;
    assert(a == 1);

    // Works with default arguments, too
    void fun2(string s = "abc") { ++a; }
    fun2;
    assert(a == 2);
}

The same goes about methods:

unittest
{
    int a;
    struct S1 { void fun1() { ++a; } }
    S1 s1;
    // will call fun
    s1.fun1;
    assert(a == 1);

    // Works with default arguments, too
    struct S2 { void fun2(string s = "abc") { ++a; } }
    S2 s2;
    s2.fun2;
    assert(a == 2);
}

However, that's not the case with function objects, delegate objects, or objects that implement the function call operator.

unittest
{
    static int a;
    static void fun1() { ++a; }
    auto p1 = &fun1;
    // Error: var has no effect in expression (p1)
    p1;
    assert(a == 0);
}
unittest
{
    int a;
    void fun1() { ++a; }
    auto p1 = &fun1;
    // Error: var has no effect in expression (p1)
    p1;
}
unittest
{
    static int a;
    struct S1 { void opCall() { ++a; } }
    S1 s1;
    // Error: var has no effect in expression (s1)    s1;
    s1;
}

Taking the type of a symbol that may be used in a paren-less call results in the type of the returned object. THIS IS A CHANGE OF SEMANTICS.

unittest
{
    int fun1() { return 42; }
    static assert(is(typeof(fun1) == int));
}

To get the function type, one must apply the address-of operator.

unittest
{
    int fun1() { return 42; }
    static assert(is(typeof(&fun1) == int delegate()));
    static int fun2() { return 42; }
    static assert(is(typeof(&fun2) == int function()));
}

The same goes about member functions. THIS IS A CHANGE OF BEHAVIOR.

unittest
{
    struct S1 { int fun() { return 42; } }
    S1 s1;
    assert(s1.fun == 42);
    static assert(is(typeof(s1.fun) == int)); // currently fails
}

The basic motivation here is that "s1.fun" should not change type when under "typeof".

If a function returns a reference, then assignment through the paren-less call should work:

unittest
{
    static int x;
    ref int fun1() { return x; }
    fun1 = 42;
    assert(x == 42);
}

A function that returns an object that in turn supports a call with "()" will never automatically apply implicit parens to the returned object. Using either `fun` or `fun()` will return the callable entity. To invoke the callable entity immediately one must use `fun()()`.

unittest
{
    static int x;
    int function() fun1() { return () => 42; }
    assert(is(typeof(fun1) == int function()));
    assert(is(typeof(fun1()) == int function()));
    assert(is(typeof(fun1()()) == int));
    assert(fun1()() == 42);
}

"Read" properties with the @property annotation

Functions annotated with @property are subject to additional restrictions compared to regular functions.

In brief, the "()" operator may NEVER be applied EXPLICITLY to a function annotated with @property. THIS IS A CHANGE OF SEMANTICS.

unittest
{
    @property int prop1() { return 42; }
    assert(prop1 == 42);
    static assert(is(typeof(prop1) == int));
    static assert(!__traits(compiles, prop1()));
}

Applying the "()" to a property will simply apply it to the result of the property. THIS IS A CHANGE OF BEHAVIOR.

unittest
{
    @property int function() prop1() { return () => 42; }
    assert(prop1() == 42);
}

(Note: The @property annotation is not part of the function type, so it is impossible for a property to return a property.)

"Write" properties via the @property annotation

In order to use the assignment operator "=" property-style, the @property annotation MUST be used.

The rule for allowing assignment with properties is simple.

1. If "foo" is a function that has the @property annotation AND takes exactly one parameter, then "foo = x" calls foo with argument x. Calling "foo(x)" is disallowed. The type of the expression "foo = x" is the type of foo's result.

unittest
{
    @property void fun(int x) { assert(x == 42); }
    fun = 42;
   assert(is(typeof(fun = 42) == void));
}

2. If "foo" is a function that has the @property annotation AND takes exactly two parameters, then "x.foo = y" calls foo with arguments x and y. Calling "foo(x, y)" or "x.foo(y)" is disallowed.

unittest
{
    @property double fun(int x, double y) { assert(x == 42 && y == 43); return y; }
    42.fun = 43;
   assert(is(typeof(42.fun = 43) == double));
}

3. If "foo" is a member function of a class or struct that has the @property annotation AND takes exactly one parameter (aside from the implicit parameter this), then "x.foo = y" calls x.foo with argument y.

unittest
{
    struct S1
    {
        @property double fun(int x) { assert(x == 42); return 43; }
    }
    S1 s1;
    s1.fun = 42;
    assert((s1.fun = 42) == 43);
    assert(is(typeof(s1.fun = 42) == double));
}

No module-level properties

There is no module-level property emulating a global variable. That means a @property defined at module level must take either one parameter (meaning it's a getter) or two parameters (meaning it's a setter).

// at module level
@property int truncated(double x) { return cast(int) x; }
@property void all(double[] x, int y) { x[] = cast(double) y; }
unittest
{
    // truncated = 4.2; // compile-time error
    int a = 4.2.truncated;
    assert(a == 4);
    auto d = [ 1.2, 3.4 ];
    d.all = 42;
    assert(d == [ 42.0, 42.0 ]);
}

Taking the address of a property

If prop is a property, &prop or a.prop obey the normal rules of function/delegate access. They do not take the addres of the returned value implicitly. To do so, one must use &(prop) or &(a.prop).

Applying operators

This may be getting a bit too cute, but there's quite some demand for it.

If a.prop is a member variable, the expression a.prop op= x has the usual meaning. Otherwise, a.prop op= x gets rewritten twice. First rewrite is (a.prop) op= x, i.e. apply op= to the result of the property. Second rewrite is a.prop = a.prop op x. If only one of the two rewrite compiles, use it. If both compile, fail with ambiguity error.

For properties, the increment operators are rewritten as follows

Rewrite 1:

++a.p ----> ++(a.p)

a.p++ ----> (++a.p)

Rewrite 2: ++a.p ----> { auto v = a.p; ++v; a.p = v; return v; }()

a.p++ ----> { auto v = a.p; ++a.p; return v; }()

If only one of the two rewrite compiles, use it. If both compile, fail with ambiguity error.

unittest

A battery of detailed and explained unittests (derived from Kenji Hara's post follows.

// Could be any type
alias Type = int;

unittest
{
   struct S
   {
       @property Type foo();       // formal getter
       @property void bar(Type);   // formal setter
       @property ref Type baz();   // ref return getter == auxiliary setter
   }

   S s;
   // Correct, normal property read
   static assert( __traits(compiles, { s.foo;     }));
   // Cannot apply "()" explicitly to a property
   static assert(!__traits(compiles, { s.foo();   }));
   // s.foo automatically applies the property
   static assert(is(typeof(s.foo) == Type));
   // Taking the address reveals the delegate
   static assert(is(typeof(&s.foo) == Type delegate()));

   // Correct, normal property write
   static assert( __traits(compiles, { s.bar = 1; }));
   // Cannot write properties with the function call syntax
   static assert(!__traits(compiles, { s.bar(1);  }));
   // A write-only property does not make sense without the assignment
   static assert(is(typeof(s.bar)) == false);
   // Taking the address reveals the delegate
   static assert(is(typeof(&s.bar) == void delegate(Type)));

   // Correct, normal property read
   static assert( __traits(compiles, { s.baz;     }));
   // Cannot use "()" with properties
   static assert(!__traits(compiles, { s.baz();   }));
   // The property is read, writing is done through the resulting ref
   static assert( __traits(compiles, { s.baz = 1; }));
   // Automatically apply "()"
   static assert(is(typeof(s.baz) == Type));
   // Taking the address reveals the delegate
   static assert(is(typeof(&s.foo) == ref Type delegate()));
   // Changing precedence with parens reveals the returned type
   static assert(is(typeof(&(s.foo)) == Type*));
}

unittest
{
   struct S
   {
       Type foo();         // 0-arg function
       void bar(Type n);   // 1-arg function
       ref Type baz();     // 0-arg ref return function
   }
   S s;

   // @@@ Review from here
   static assert( __traits(compiles, { s.foo;     }));
   static assert( __traits(compiles, { s.foo();   }));
   static assert(is(typeof(s.foo) == Type));
   static assert(is(typeof(&s.foo) == Type delegate()));

   static assert(!__traits(compiles, { s.bar = 1; }));
   static assert( __traits(compiles, { s.bar(1);  }));
   static assert(is(typeof(s.bar)) == false);
   static assert(is(typeof(&s.bar) == void delegate(Type)));

   static assert( __traits(compiles, { s.baz;     }));
   static assert( __traits(compiles, { s.baz = 1; }));
   static assert( __traits(compiles, { s.baz();   }));
   static assert(is(typeof(s.baz) == Type));
   static assert(is(typeof(&s.baz) == ref Type delegate()));
}

@property Type foo();
@property void bar(Type);
@property ref Type baz();

unittest
{
   static assert( __traits(compiles, { foo;     }));
   static assert(!__traits(compiles, { foo();   }));
   static assert(is(typeof(foo) == Type));
   static assert(is(typeof(&foo) == Type function()));

   static assert( __traits(compiles, { bar = 1; }));
   static assert(!__traits(compiles, { bar(1);  }));
   static assert(is(typeof(bar)) == false);
   static assert(is(typeof(&bar) == Type function()));

   static assert( __traits(compiles, { baz;       }));
   static assert(!__traits(compiles, { baz();     }));
   static assert( __traits(compiles, { baz = 1;   }));
   static assert(!__traits(compiles, { baz() = 1; }));
   static assert(is(typeof(baz) == Type));
   static assert(is(typeof(&baz) == ref Type function()));
}

@property Type foh(Type);
@property void bah(Type n, Type m);
@property ref Type bas(Type);

Type hoo(Type);
void var(Type, Type);
ref Type vaz(Type);

unittest
{
   static assert( __traits(compiles, { foh = 1; })     &&
!__traits(compiles, { hoo = 1; }));
   static assert(!__traits(compiles, { foh(1);  })     &&
__traits(compiles, { hoo(1);  }));
   static assert(!__traits(compiles, { 1.foh;   })     &&
__traits(compiles, { 1.hoo;   }));
   static assert(!__traits(compiles, { 1.foh(); })     &&
__traits(compiles, { 1.hoo(); }));

   static assert(!__traits(compiles, { bah(1, 2); })   &&
__traits(compiles, { var(1, 2); }));
   static assert( __traits(compiles, { 1.bah = 2; })   &&
!__traits(compiles, { 1.var = 2; }));
   static assert(!__traits(compiles, { 1.bah(2);  })   &&
__traits(compiles, { 1.var(2);  }));

   static assert( __traits(compiles, { bas = 1;     }) &&
!__traits(compiles, { vaz = 1;     }));
   static assert(!__traits(compiles, { bas(1);      }) &&
__traits(compiles, { vaz(1);      }));
   static assert(!__traits(compiles, { bas(1) = 2;  }) &&
__traits(compiles, { vaz(1) = 2;  }));
   static assert(!__traits(compiles, { 1.bas;       }) &&
__traits(compiles, { 1.vaz;       }));
   static assert(!__traits(compiles, { 1.bas = 2;   }) &&
__traits(compiles, { 1.vaz = 2;   }));
   static assert(!__traits(compiles, { 1.bas();     }) &&
__traits(compiles, { 1.vaz();     }));
   static assert(!__traits(compiles, { 1.bas() = 2; }) &&
__traits(compiles, { 1.vaz() = 2; }));
}

Copyright

This document has been placed in the Public Domain.