All: I'll be continuing work on the RFC tomorrow, it is still in draft. Yasuo: I'll read through your notes tomorrow, thanks for detailed input.
Cheers Joe On Sat, Feb 14, 2015 at 9:17 AM, Yasuo Ohgaki <yohg...@ohgaki.net> wrote: > Hi Francois, > > Now I understand what you are discussing. Since we may have stricter > typing, we probably better > to consider type safety theory even if PHP is weakly typed. > > What I'm going to write is not type theory, though. > > On Sat, Feb 14, 2015 at 3:16 PM, François Laupretre <franc...@php.net> > wrote: > > > > The theory (from Eiffel guru) states that, to respect this fucking LSP > rule, the pre-conditions to check when entering a method must be less > strict than when entering its parent method. Don’t ask why, I don’t > understand the reason very well. But that’s his rule, and everyone seems to > respect it. > > > > > > > > In your RFC, you say that, when we enter a method, we must check its > pre-conditions, as well as the ones of every parents. As you can only add > conditions, compared to what you would do for the parent, the checks can > only be stricter (more conditions). > > > > Pre/Postconditions should be checked only when parent method is called. > That's what Eiffel does. > Eiffel does not allow method overriding. Therefore, pre/postconditions of > methods (not invariants. > Invariants inherited implicitly(automatically) both Eiffel and D are > evaluated except certain > methods) cannot be evaluated automatically unless parent method is called. > > public function foo() { > parent::foo(); // pre/post conditions are evaluated upon call > } > > Since children's method may have whole different way of handing > parameters, > including swapping parameter order, adding/removing parameters, etc. > Parameters cannot be checked automatically unless we have some kind of > binding system that bind child parameter to parent parameter. Even if we > have > it, parameter for parents may be generated in function body. It cannot be > perfect. > > Therefore, overridden method's parent precondition evaluation cannot be > done > until child calls it explicitly. Postcondition is the same. Child object > may > return whatever return value it returns, there is no point checking parent > method's postcondition automatically. > > Invariant are also pre/postcondition, but it differs. > > > The logic described in the D documentation is : if a method defines > pre-conditions, check them and stop (don’t check parent’s pre-conditions). > If the method does not define pre-conditions, go down one level and check > if parent method defines some. As soon as a method defining pre-conditions > is found, these are checked and control is returned without going further. > This way, it is still the developer’s responsibility to loosen conditions > in derived classes but it is possible. If you check every parent’s > pre-conditions, it is just *not* possible. > > > > Basic rule is we shouldn't be able to modify parent contracts(invariant, > methods pre/postconditions). > If we can change it, it's the same as changing type. Your discussion > applies to invariant and this > is what you write, I suppose. > > > Child only can strengthen contract(invariant) e.g. > > age >= 0 (Human) The base class. Followings are children. > age >= 18 (Adult) 18 or over is greater than 0. OK > age <18 (Child) 0 to 18 are greater than 0. OK > age < 0 (Alien) This cannot happen as Human subtype. It violates Human > type > > Type safety is protected by invariant like this. > > > > > > I am not sure I am clear. > > > > > > > > I chose not to follow exactly this logic as I think we can do it more > ‘PHP way’ (something like the way constructors and destructors explicitly > call their parent methods). My logic is : if the method we are entering has > no pre-conditions, we don’t check anything (don’t search a parent method). > If it defines some, we execute them. I introduce a ‘special’ condition : > the ‘@parent’ pseudo-condition means ‘go down checking my parent’s > conditions’. This way, similar to ‘parent::__construct()’ logic allows the > developer to decide if he wants to check parent’s conditions or not. > > > > We should only evaluate method's contract(pre/postcondition) when it is > called. > We should always evaluate class contract(invariant) including parents > when it is applicable. (exceptions are __construct/_destruct/etc) > > > > > > > Example : > > > > > > > > /** > > > > * @requires ($a < $b) > > > > * @requires @parent <- Go checking parent’s pre-conditions > > > > * @requires … > > > > > > > > For better consistence, I chose to implement the same for > post-conditions and invariants, but I am not sure I will keep > > > > the same logic. > > > > > > > > The only thing that remains not clear for me is the role of conditions > defined in interfaces, as the D documentation says that they can be defined > but it does not explain when they are checked. I assume this is considered > as a parent but the order matters in pre-conditions as we only execute the > first conditions we find. I think I’ll assume that ‘@parent’ means ‘check > the conditions defined in the parent method and the method of the interface > that defines it’. Unfortunately, interfaces have parents too and can define > methods with same name. So, it’s the same sort of problems as multiple > inheritance. I will need to make a choice here. The simplest one would be > ‘no support for DbC in interfaces’. > > The same rule for class applies to interfaces. > > We should only evaluate method's contract(pre/postcondition) when it is > called. > We should always evaluate class contract(invariant) including parents > when it is applicable. (exceptions are __construct/_destruct/etc) > > /* contracts are omitted */ > class B extends A { > function __construct() { > parent::__construct() > } > } > > When constructor is called, these are the detailed order. > > B::__construct() precondition evaluation > B invariant evaluation skipped. Constructor is special. > A invariant evaluation skipped. Constructor is special. > /* function body of B. Calls parent::__construct() */ > A::__construct() precondition evaluation > /* function body of A */ > A::__construct() postcondition evaluation > /* function body of B.*/ > A invariant evaluation > B invariant evaluation > B::__construct postcondition evaluation > > Developers can ignore parent constructor pre/postconditions. If it does > matter to developers, it's okay. > PHP( and other languages) allows whatever bad design. > > We cannot ignore parent class invariants. If developer violate parent > property restrictions, > it's violation of parent class type and this must be forbidden. It's > checked by invariant contract. > > I don't think I explained well, but you might be able to understood me. > Keeping it simple works. We need no special handlings. > > Regards, > > -- > Yasuo Ohgaki > yohg...@ohgaki.net >