How does purity flow through multi-level inheritance chains?
← Purity Contracts · Ref: Q567
Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure).
Purity flows through the entire inheritance chain. If a grandparent method is pure, the parent override must be pure, and the grandchild override must also be pure. Every level must maintain the contract.
MULTI-LEVEL RULE
Grandparent declares method as pure. Parent override must be pure (E05150 if not). Grandchild override must be pure (E05150 if not). The chain never breaks.
WHY EVERY LEVEL MATTERS
Code using the grandparent type expects purity. The actual runtime object might be any descendant. Every descendant must honour the original contract.
PRACTICAL PATTERN
Design purity at the root of the hierarchy:
Animal as abstract sound() as pure abstract Dog extends Animal override sound() as pure Labrador extends Dog override sound() as pure
CANNOT ADD PURE LATER
If the grandparent is NOT pure, no descendant can add purity (E05160). Purity is designed in at the top.
See Q561 for purity override rules. See Q562 for cannot-add-pure. See Q575 for deep override chains. See Q568 for pure operators.
See Q677 for abstract implementation chain. See Q678 for purity override contract. See Q699 for return type covariance.
Example
defines module qa.purity.chaininheritance defines class //Level 1: declares pure contract Animal as abstract sound() as pure abstract <- rtn as String? describe() as pure abstract <- rtn as String? default operator ? //Level 2: must maintain pure Dog extends Animal as open override sound() as pure <- rtn as String: "Woof" override describe() as pure <- rtn as String: "Dog" default operator ? //Level 3: must STILL maintain pure Labrador extends Dog override sound() as pure <- rtn as String: "Woof woof!" override describe() as pure <- rtn as String: "Labrador" default operator ? //Another chain: Cat hierarchy Cat extends Animal as open override sound() as pure <- rtn as String: "Meow" override describe() as pure <- rtn as String: "Cat" default operator ? Siamese extends Cat override sound() as pure <- rtn as String: "Mrow!" override describe() as pure <- rtn as String: "Siamese" default operator ? defines program PureChainDemo() stdout <- Stdout() //All levels honour the pure contract animals <- List() of Animal animals += Labrador() animals += Siamese() for animal in animals stdout.println(`${animal.describe()} says: ${animal.sound()}`)
Common mistakes
E05150 — At every level of the inheritance chain, the pure contract must be maintained. The abstract Animal.sound is pure, so Dog.sound and Labrador.sound must also be pure. See ek9 -h E05150 for details.
Incorrect:
override sound()
Correct:
override sound() as pure
E50050 — Even though describe is correctly declared as pure, calling a non-pure method from within it would violate the purity contract transitively. See ek9 -h E50050 for details.
Incorrect:
override describe() as pure <- rtn as String: nonPureHelper()
Correct:
override describe() as pure
Other ways to ask this
- Does purity apply to grandchild classes?
- How does the pure contract carry through three levels of inheritance?
- Must every level maintain purity?
Coming from another language?
Java: no multi-level purity tracking. Kotlin: no purity. EK9: purity contract is enforced at every level in the inheritance chain via E05150.
Keywords: multi-level, pure, grandchild, inheritance, contract, side-effect, grandparent, chain, E05150, purity, immutable