How does purity interact with abstract function extension?

← Function Extension · Ref: Q595

If an abstract function is declared 'as pure', every implementation must also be 'as pure'. This is the same E05150 rule that applies to class method overrides. Purity is a contract from the abstract definition.

PURE ABSTRACT FUNCTION

When an abstract function is pure, the contract guarantees no side effects for all implementations:

  Hasher as pure abstract
    -> input as String
    <- hash as Integer?

IMPLEMENTATIONS MUST BE PURE

Every function using 'is Hasher' must also be pure:

  SimpleHasher is Hasher as pure
    -> input as String
    <- hash as Integer: #? input

CANNOT ADD PURITY

If the abstract function is NOT pure, implementations cannot add purity (E05160). Purity flows from the abstract definition.

WHY PURITY MATTERS FOR FUNCTIONS

Code using the abstract type expects consistent behavior. If some implementations are pure and others are not, callers cannot reason about side effects.

See Q560 for pure method basics. See Q561 for purity in overrides. See Q588 for function extension basics. See Q594 for extension rules.

Example

defines module qa.function.pureabstract

  defines function

    //Pure abstract: all implementations must be pure
    Scorer as pure abstract
      -> text as String
      <- score as Integer?

    LengthScorer is Scorer as pure
      -> text as String
      <- score as Integer: length text

    UpperScorer is Scorer as pure
      -> text as String
      <- score as Integer: length text.upperCase()

    //Non-pure abstract: implementations can be impure
    Reporter as abstract
      -> message as String
      <- report as String?

    SimpleReporter is Reporter
      -> message as String
      <- report as String: "[REPORT] " + message

  defines program

    PureAbstractFunctionDemo()
      stdout <- Stdout()

      //Pure function hierarchy
      scorers <- List() of Scorer
      scorers += LengthScorer
      scorers += UpperScorer

      for scorer in scorers
        stdout.println(`Score: ${scorer("Hello World")}`)

      //Non-pure function hierarchy
      reporter <- SimpleReporter
      stdout.println(reporter("system ready"))

Common mistakes

E05150 — When the abstract function is declared as pure, every implementation must also be pure. Omitting 'as pure' violates the purity contract. See ek9 -h E05150 for details.

Incorrect:

LengthScorer is Scorer
      -> text as String
      <- score as Integer: length text

Correct:

LengthScorer is Scorer as pure
      -> text as String
      <- score as Integer: length text

E50001 — A pure function cannot call non-pure methods like stdout.println(). The purity contract inherited from the abstract function forbids side effects. See ek9 -h E50001 for details.

Incorrect:

UpperScorer is Scorer as pure
      -> text as String
      <- score as Integer: length text.upperCase()
      stdout.println(score)

Correct:

UpperScorer is Scorer as pure
      -> text as String
      <- score as Integer: length text.upperCase()
Other ways to ask this
  • Must implementations of a pure abstract function be pure?
  • What is E05150 for function extension?
  • How does purity flow through function hierarchies?

Coming from another language?

Java: no purity on functional interfaces. Kotlin: no purity on function types. Rust: Fn traits have no purity annotation. EK9: pure abstract functions enforce purity on all implementations via E05150.

Keywords: function, extension, pure, contract, side-effect, implementation, extend, abstract, E05150, purity, immutable