Thread overview | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
October 09, 2016 Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
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 } |
October 09, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to mikey | 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
|
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to Antonio Corbi | 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.
|
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to mikey | 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 |
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to mikey | 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 |
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to Antonio Corbi | 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
|
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | 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 );
}
|
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to mikey | 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); } |
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to mikey | 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.
|
October 12, 2016 Re: Continued looking at properties in D - interfaces and constraints | ||||
---|---|---|---|---|
| ||||
Posted in reply to mikey | 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 |
Copyright © 1999-2021 by the D Language Foundation