How do traits interact with pure method contracts?
← Purity Contracts · Ref: Q569
Traits can declare methods as pure, and any class implementing the trait must maintain that purity contract. This is the same E05150 rule that applies to class inheritance.
PURE METHODS IN TRAITS
A trait can declare abstract pure methods:
defines trait Measurable measure() as pure abstract <- rtn as Float?
Any class implementing Measurable must provide a pure override.
PURE DEFAULT METHODS
Traits can have default method implementations that are pure:
Describable
label() as pure
<- rtn as String: "unknown"
Classes can override the default but must keep it pure.
IMPLEMENTATION RULE
When a class uses 'with trait of' and overrides a pure trait method, the override must be pure (E05150):
MyClass with trait of Measurable override measure() as pure <- rtn as Float: 42.0
MULTIPLE TRAITS
If a class implements multiple traits, each pure method contract is independently enforced. A method that satisfies a pure trait must be pure.
See Q106 for trait basics. See Q107 for multiple traits. See Q561 for purity override rules. See Q567 for purity chains.
Example
defines module qa.purity.traitpure defines trait //Trait requiring pure methods Measurable measure() as pure abstract <- rtn as Float? //Trait with pure default method Labelable label() as pure <- rtn as String: "unlabeled" defines class //Must keep measure() pure because trait requires it Box with trait of Measurable, Labelable width <- 0.0 height <- 0.0 Box() -> width as Float height as Float this.width: width this.height: height //Pure override satisfies Measurable trait override measure() as pure <- rtn as Float: width * height //Pure override satisfies Labelable trait override label() as pure <- rtn as String: `Box ${width}x${height}` default operator ? //Another implementation of same traits Sphere with trait of Measurable, Labelable radius <- 0.0 Sphere() -> radius as Float this.radius: radius override measure() as pure <- rtn as Float: 4.0 * 3.14159 * radius * radius override label() as pure <- rtn as String: `Sphere r=${radius}` default operator ? defines program TraitPureMethodsDemo() stdout <- Stdout() box <- Box(3.0, 4.0) stdout.println(`${box.label()}: ${box.measure()}`) sphere <- Sphere(5.0) stdout.println(`${sphere.label()}: ${sphere.measure()}`) //Polymorphic use through trait items <- List() of Measurable items += Box(2.0, 3.0) items += Sphere(1.0) for item in items stdout.println(`Measurement: ${item.measure()}`)
Common mistakes
E05150 — The Measurable trait declares measure as pure abstract. The implementing class must maintain purity. Overriding without as pure violates the trait contract. See ek9 -h E05150 for details.
Incorrect:
override measure()
Correct:
override measure() as pure
E05150 — The Labelable trait provides a pure default for label. Overriding it without as pure would violate the purity contract inherited from the trait. See ek9 -h E05150 for details.
Incorrect:
override label()
Correct:
override label() as pure
Other ways to ask this
- Can a trait require pure methods?
- Must a class implementing a trait maintain purity?
- How does purity work with trait default methods?
Coming from another language?
Java: interfaces can have default methods but no purity concept. Kotlin: interfaces with defaults but no purity. Rust: traits with default implementations but no purity markers. EK9: traits can declare pure methods, implementations must maintain purity via E05150.
Keywords: method, pure, default, trait, contract, side-effect, implement, abstract, E05150, purity, immutable