Why can't I inject components in a pure context?

← DI Validation · Ref: Q666

Component injection is an impure operation because it involves runtime state management (E08140). The DI container resolves and provides instances at runtime, which conflicts with purity guarantees.

WHY INJECTION IS IMPURE

Pure functions guarantee no side effects and deterministic results. Injection accesses mutable singleton state managed by the DI container, making the result non-deterministic.

CORRECT PATTERN

Inject at the field level, then pass data to pure methods:

  defines component
    MyComponent
      repository as Repository!
      processData() as pure
        -> items as List of String
        <- count as Integer: length items

The impure injection happens at field level. Pure methods receive data through parameters.

See Q111 for component basics. See Q560 for purity contracts. See Q667 for abstract injection.

Example

defines module qa.divalidation.purecontext

  defines component

    <?-
      Abstract component defining repository contract.
    -?>
    DataStore as abstract

      fetchAll() as abstract
        <- rtn as List of String?

      default operator ?

    <?-
      Concrete implementation of the data store.
    -?>
    InMemoryStore extends DataStore

      override fetchAll()
        <- rtn <- List() of String
        rtn += "record-alpha"
        rtn += "record-beta"

      default operator ?

  defines function

    <?-
      Pure function receives data as parameter,
      not through injection. This is the correct pattern
      for processing data without DI coupling.
    -?>
    countRecords() as pure
      -> records as List of String
      <- total as Integer: length records

  defines application

    DataApp
      register InMemoryStore() as DataStore

  defines program

    InjectionPureContextDemo() with application of DataApp
      stdout <- Stdout()

      store as DataStore!

      allRecords <- store.fetchAll()
      stdout.println(`Records fetched: ${length allRecords}`)

      recordCount <- countRecords(allRecords)
      stdout.println(`Count via pure function: ${recordCount}`)

Common mistakes

E08140 — Component injection with '!' is an impure operation (accesses DI container state). A pure method cannot contain injection points. Pass the component as a parameter instead. See ek9 -h E08140 for details.

Incorrect:

      override fetchAll()
        <- rtn <- List() of String
        rtn += "record-alpha"
        rtn += "record-beta"

      pureButBroken() as pure
        injected as DataStore!
        require injected?

Correct:

      override fetchAll()
        <- rtn <- List() of String
        rtn += "record-alpha"
        rtn += "record-beta"
Other ways to ask this
  • What is E08140 injection in pure context?
  • Why does injection conflict with purity?
  • How do I use components in pure methods?

Coming from another language?

Java: Spring allows injection everywhere (no purity concept). Python: no DI purity enforcement. Go: no DI framework. Rust: no runtime DI. EK9: injection is impure by design, use parameter passing for pure methods.

Keywords: registration, side-effect, text, context, E08140, component, validate, injection, DI, immutable, inject, pure, impure