www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Continued looking at properties in D - interfaces and constraints

reply mikey <abc.mikey googlemail.com> writes:
Hi,

I'm continuing to look at properties in D and have found another 
area where I think there may be an issue - or maybe where I'm 
doing something wrong.

I have started trying to use constraints on my properties to 
constrain which values they can take I have also started trying 
to use interfaces. What I noticed was that when I combine these 2 
features the constraints get discarded.

     interface Widthy {
          property inout(int) width() inout;
          property void width(int width);
     }

     class Test : Widthy {
     private:
         int _w;
     public:
          property inout(int) width() inout { return _w; }
          property void width(int width)
         in {
             import std.exception;
             if (width < 0) {
                 throw new
                 Exception("width is less than zero");
             }
         }
         body {
             _w = width;
         }
     }

     void main() {
         import std.stdio;
         auto t = new Test;
         t.width = -1;
         writeln("width: ", t.width);
         // width: -1

         // hmmm... not good
     }
Oct 09 2016
parent reply Antonio Corbi <amcb ggmail.com> writes:
On Sunday, 9 October 2016 at 11:54:50 UTC, mikey wrote:
 Hi,

 I'm continuing to look at properties in D and have found 
 another area where I think there may be an issue - or maybe 
 where I'm doing something wrong.

 I have started trying to use constraints on my properties to 
 constrain which values they can take I have also started trying 
 to use interfaces. What I noticed was that when I combine these 
 2 features the constraints get discarded.

     interface Widthy {
          property inout(int) width() inout;
          property void width(int width);
     }

     class Test : Widthy {
     private:
         int _w;
     public:
          property inout(int) width() inout { return _w; }
          property void width(int width)
         in {
             import std.exception;
             if (width < 0) {
                 throw new
                 Exception("width is less than zero");
             }
         }
         body {
             _w = width;
         }
     }

     void main() {
         import std.stdio;
         auto t = new Test;
         t.width = -1;
         writeln("width: ", t.width);
         // width: -1

         // hmmm... not good
     }
Hi Mikey, I think the failure you are experimenting is a mixture of two problems: 1. Inheritance with contracts is evaluated in a special way, 'in contracts' in the base and derived method (property) are or-ed, so if one of them passses, the contract is believed to have succeeded. As you don't have a contract in the base-method: " property void width(int width)", I think that the compiler assumes that its empty contract is always true. So it's a matter of adding some contract to it, i.e.: interface Widthy { property inout(int) width() inout; property void width(int w) in { assert(w > 7); } } But then comes... 2. This approach fails with dmd version: DMD64 D Compiler v2.071.2 but it works with ldc 1.0.0 and also with ldc-git (based on DMD v2.071.2 and LLVM 3.8.1 , built with DMD64 D Compiler v2.071.2), so I think it's a bug in DMD, but I'm very new to D, so I hope that someone here with deeper knowledge can throw some light :) Antonio
Oct 09 2016
next sibling parent reply mikey <abc.mikey googlemail.com> writes:
On Sunday, 9 October 2016 at 14:06:42 UTC, Antonio Corbi wrote:
 1. Inheritance with contracts is evaluated in a special way, 
 'in contracts' in the base and derived method (property) are 
 or-ed, so if one of them passses, the contract is believed to 
 have succeeded. As you don't have a contract in the 
 base-method: " property void width(int width)", I think that 
 the compiler assumes that its empty contract is always true.
Doesn't sound right to me. "Or"ing contracts with different inherited methods can't possibly be a feature. I'll create a bug report for it.
Oct 11 2016
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Wednesday, 12 October 2016 at 06:20:05 UTC, mikey wrote:
 On Sunday, 9 October 2016 at 14:06:42 UTC, Antonio Corbi wrote:
 1. Inheritance with contracts is evaluated in a special way, 
 'in contracts' in the base and derived method (property) are 
 or-ed, so if one of them passses, the contract is believed to 
 have succeeded. As you don't have a contract in the 
 base-method: " property void width(int width)", I think that 
 the compiler assumes that its empty contract is always true.
Doesn't sound right to me. "Or"ing contracts with different inherited methods can't possibly be a feature. I'll create a bug report for it.
https://dlang.org/spec/contracts.html#in_out_inheritance
Oct 11 2016
prev sibling parent Antonio Corbi <amcb ggmail.com> writes:
On Wednesday, 12 October 2016 at 06:20:05 UTC, mikey wrote:
 On Sunday, 9 October 2016 at 14:06:42 UTC, Antonio Corbi wrote:
 1. Inheritance with contracts is evaluated in a special way, 
 'in contracts' in the base and derived method (property) are 
 or-ed, so if one of them passses, the contract is believed to 
 have succeeded. As you don't have a contract in the 
 base-method: " property void width(int width)", I think that 
 the compiler assumes that its empty contract is always true.
Doesn't sound right to me. "Or"ing contracts with different inherited methods can't possibly be a feature. I'll create a bug report for it.
Hi Mikey, It's the way DbC (Design by Contract) works, have a look at point 8 in https://www.eiffel.com/values/design-by-contract/introduction/ Antonio
Oct 12 2016
prev sibling parent reply Jonathan M Davis via Digitalmars-d-learn writes:
On Sunday, October 09, 2016 14:06:42 Antonio Corbi via Digitalmars-d-learn 
wrote:
 I think that the compiler assumes that its empty contract is always true.
That's exactly what it does. Having no contract is considered to be equivalent to having an empty contract. So, because an empty contract will never fail, not having an in contract in the base class (or interface) function is equivalent to not having any in contracts anywhere in the inheritance chain, which is pretty annoying. The ||ing and &&ing done with in and out contracts and inheritance is critical to having contracts work correctly with inheritance. And in contracts work properly when each level declares one, but it probably would be better if having an in contract were illegal if the base class didn't have one, since then you'd at least avoid accidentally having an in contract that's pointless. out contracts shouldn't have any of these problems though, since they're &&ed, and &&ing with true doesn't change anything. Missing in contracts definitely is an easy way to introduce bugs into your contracts though. - Jonathan M Davis
Oct 12 2016
parent reply mikey <abc.mikey googlemail.com> writes:
On Wednesday, 12 October 2016 at 08:36:43 UTC, Jonathan M Davis 
wrote:
 That's exactly what it does. Having no contract is considered 
 to be equivalent to having an empty contract. So, because an 
 empty contract will never fail, not having an in contract in 
 the base class (or interface) function is equivalent to not 
 having any in contracts anywhere in the inheritance chain, 
 which is pretty annoying. The ||ing and &&ing done with in and 
 out contracts and inheritance is critical to having contracts 
 work correctly with inheritance. And in contracts work properly 
 when each level declares one, but it probably would be better 
 if having an in contract were illegal if the base class didn't 
 have one, since then you'd at least avoid accidentally having 
 an in contract that's pointless. out contracts shouldn't have 
 any of these problems though, since they're &&ed, and &&ing 
 with true doesn't change anything. Missing in contracts 
 definitely is an easy way to introduce bugs into your contracts 
 though.

 - Jonathan M Davis
OK I'm somewhat struggling with this concept. As I see it the "in" contract can only place a limit on what values are valid to come in to the method. I could maybe see some questionable argument to make any contract for values coming into the method a subset of the previous valid values, but what you are describing is the opposite: that each contract has to be a superset of all the previous contracts for valid values! This seems limiting at best, but worse completely at odds with the concept of inheritance building from the most general to the most specific case. Say I have worker: import std.exception; class Worker { private: uint _wage = 10_000; public: property uint wage() { return _wage; } property void wage(uint wage) in { enforce(wage >= 10_000 && wage <= 1_000_000_000); } body { _wage = wage; } } class WageSlave : Worker { private: uint _wage = 10_000; public: override property uint wage() { return _wage; } override property void wage(uint wage) in { enforce(wage >= 10_000 && wage <= 40_000); } body { _wage = wage; } } class CEO : Worker { private: uint _wage = 1_000_000; public: override property uint wage() { return _wage; } override property void wage(uint wage) in { enforce(wage >= 1_000_000 && wage <= 1_000_000_000); } body { _wage = wage; } } void main() { auto worker = new Worker; assertThrown( worker.wage = 9_999 ); assertThrown( worker.wage = 1_000_000_001 ); assertNotThrown( worker.wage = 10_000 ); assertNotThrown( worker.wage = 1_000_000_000 ); auto slave = new WageSlave; assertThrown( slave.wage = 9_999 ); assertThrown( slave.wage = 1_000_000_001 ); assertNotThrown( slave.wage = 10_000 ); assertNotThrown( slave.wage = 1_000_000_000 ); // BAD - no throw auto ceo = new CEO; assertThrown( ceo.wage = 9_999 ); assertThrown( ceo.wage = 1_000_000_001 ); assertNotThrown( ceo.wage = 10_000 ); // BAD - no throw assertNotThrown( ceo.wage = 1_000_000_000 ); }
Oct 12 2016
next sibling parent reply mikey <abc.mikey googlemail.com> writes:
Also, accepting that "in" contracts should be "or"ed, interfaces 
still seem broken to me:

     import std.exception;

     interface Widthy {
          property inout(int) width() inout;
          property void width(int width) in { enforce(width < 0); }
     }

     class Test : Widthy {
     private:
         int _w;
     public:
          property inout(int) width() inout { return _w; }
          property void width(int width) in { enforce(width < 0); }
         body {
             _w = width;
         }
     }

     void main() {
         import std.stdio;
         auto t = new Test;
         t.width = -1;
         writeln("width: ", t.width);
         // width: -1

         // doesn't look right
     }

It does however behave how you describe if you use inheritance 
with 2 classes, or if you change the interface property's 
contract to:

     property void width(int width) in { assert(0); }
Oct 12 2016
next sibling parent mikey <abc.mikey googlemail.com> writes:
On Wednesday, 12 October 2016 at 20:49:40 UTC, mikey wrote:
          property void width(int width) in { enforce(width < 
 0); }
Arg! Stupid, I switched those around to enforces at the last minute, the sign should have been the other way round! Anyway the output is the same either way.
Oct 12 2016
prev sibling parent ag0aep6g <anonymous example.com> writes:
On 10/12/2016 10:49 PM, mikey wrote:
     import std.exception;

     interface Widthy {
          property inout(int) width() inout;
          property void width(int width) in { enforce(width < 0); }
     }

     class Test : Widthy {
     private:
         int _w;
     public:
          property inout(int) width() inout { return _w; }
          property void width(int width) in { enforce(width < 0); }
         body {
             _w = width;
         }
     }

     void main() {
         import std.stdio;
         auto t = new Test;
         t.width = -1;
         writeln("width: ", t.width);
         // width: -1

         // doesn't look right
     }
The contract of the interface seems to see a wrong value: ---- import std.stdio; interface Widthy { void width(int width) in { writeln(width); /* prints "2" */ assert(width > 0); } } class Test : Widthy { void width(int width) in { assert(width > 0); } body {} } void main() { auto t = new Test; t.width(-1); } ---- I haven't checked closely, but there's issue 15984 which is titled "Interface contracts retrieve garbage instead of parameters". That's probably it. It's a regression in 2.071. https://issues.dlang.org/show_bug.cgi?id=15984
Oct 12 2016
prev sibling next sibling parent ag0aep6g <anonymous example.com> writes:
On 10/12/2016 10:40 PM, mikey wrote:
     import std.exception;

     class Worker {
     private:
         uint _wage = 10_000;

     public:
          property uint wage() { return _wage; }
          property void wage(uint wage)
         in {
             enforce(wage >= 10_000 && wage <= 1_000_000_000);
         } body {
             _wage = wage;
         }
     }

     class WageSlave : Worker {
     private:
         uint _wage = 10_000;

     public:
         override  property uint wage() { return _wage; }
         override  property void wage(uint wage)
         in {
             enforce(wage >= 10_000 && wage <= 40_000);
         } body {
             _wage = wage;
         }
     }
[...]
     void main() {
[...]
         auto slave = new WageSlave;
         assertThrown( slave.wage = 9_999 );
         assertThrown( slave.wage = 1_000_000_001 );
         assertNotThrown( slave.wage = 10_000 );
         assertNotThrown( slave.wage = 1_000_000_000 ); // BAD - no throw
[...]
     }
The behavior you expect would break substitutability [1]. Consider: ---- void f(Worker w) { w.wage = 50_000; /* Contract of Worker says I can do this. */ } void main() { f(new Worker); /* Obviously ok. */ f(new WageSlave); /* Must work. A WageSlave object must be usable as a Worker object. */ } ---- [1] https://en.wikipedia.org/wiki/Liskov_substitution_principle
Oct 12 2016
prev sibling parent Jonathan M Davis via Digitalmars-d-learn writes:
On Wednesday, October 12, 2016 20:40:57 mikey via Digitalmars-d-learn wrote:
 OK I'm somewhat struggling with this concept.
It is a bit weird. But consider class A { ... auto foo(int i) in { assert(i < 10); } out(r) { assert(r > 20); } body {...} ... } class B : A { ... override auto foo(int i) in { assert(i < 42); } out(r) { assert(r > 30); } body {...} ... } A a = func(); a.foo(5); a could be an A or a B. If it's an A, then the argument has to pass the in countract on A.foo, and the contract on B.foo doesn't matter, because it's not a B. And if a is actually a B, then the argument must _still_ pass A.foo's contract, because it's unknown from the caller's perspective whether it's an A or a B or some other class derived from A. B.foo can't have a tighter contract than A.foo, because that would violate the requirements of A.foo. If it were otherwise, if func() were changed to return a B instead of an A, the code would break even though it's using A's API and not B's. If B.foo required that i be less than 5, then suddenly code that worked with 6 would fail because of an in constraint that wasn't part of the API being used. So, tighter in contracts on derived class functions violate the contract of the base class function and cannot be allowed. However, B.foo _can_ have a looser in contract than A.foo, because if you're using it as an A, then everything that you can pass to A still works, and if you're using it as a B, and it accepts more than an A, so what? That's not a problem. You're using B's API and its requirements, not A's. So, by ||ing A.foo's and B.foo's in contracts, it allows B.foo to have a weaker contract, but it still requires that A.foo's contract be true. An out contract, on the other hand, is about guaranteeing that the result meets certain conditions. Whether the object is an A or a B, calling foo on an A reference must give a result that meets the out contract of A.foo. And B.foo's out contract cannot widen the conditions, otherwise, it would violate the out contract for A.foo. It can make them stronger, because it would still meet the requirements of A.foo, but it can't make them weaker. And if B.foo's out contract is stronger than A.foo's out contract, then when the object is a B, it has to meet both contracts, not just one of them, because if it just met A.foo's contract, then it would be violating the out contract on the function actually being called, but it still has to meet A.foo's contract, not just B.foo's contract, because if it doesn't, then when you the B is referred to be an A reference, it would be violating A's API. So, the out contracts get &&ed together. Of course, you can always craft contracts that cause problems, but the compiler and runtime try and ensure that your contracts follow the logic required for a derived class to work correctly when referred to by a reference for a base class and while ensuring that the derived class does not violate the requirements of the API of the base class when the typeo of the reference is the derived class.
          override  property void wage(uint wage)
          in {
              enforce(wage >= 10_000 && wage <= 40_000);
          } body {
              _wage = wage;
          }
      }
Contracts are supposed to use assertions, since they're for checking code validity. And using exceptions doesn't buy you anything, because contracts get compiled out in -released just like the assertions do. You're just making it so that your function can't be nothrow (though there's an open bug - https://issues.dlang.org/show_bug.cgi?id=13123 - for the compiler not catching that a nothrow function can throw thanks to contracts). If you're trying to check the input and have the function throw so that the program can try and recover from the bad input rather than treating it as a logic error, then you need to throw an exception from inside the function body and not the in contract. - Jonathan M Davis
Oct 12 2016