What happens when a dispatcher entry and handler have different purity?

← Dispatcher Validation · Ref: Q612

A dispatcher has ONE purity, set by its entry point, and every handler must match it. If the entry is 'as pure', every handler must also be 'as pure'. If the entry is not pure, no handler may be 'as pure'. Either mismatch raises E05170, and the message says which way it failed.

WHY THE HANDLERS MUST MATCH

Callers only ever see the entry point; the dispatcher routes their call to whichever handler matches the argument's runtime type. The entry point's purity is therefore the promise made about every handler. A non-pure handler would break a pure entry point's promise, and a pure handler behind a non-pure entry point is a claim no caller can rely on.

PURE DISPATCHER PATTERN

Mark the entry and all handlers as pure:

  PureCatalog
    describe() as pure dispatcher
      -> item as Item
      <- rtn as String: item.name()
    describe() as pure
      -> item as Book
      <- rtn as String: "Book: " + item.name()

NON-PURE DISPATCHER PATTERN

Omit 'as pure' from the entry AND from every handler:

  LoggingCatalog
    label() as dispatcher
      -> item as Item
      <- rtn as String: item.name()
    label()
      -> item as Book
      <- rtn as String: "Book: " + item.name()

BOTH DIRECTIONS ARE FORBIDDEN

You cannot have a pure entry with a non-pure handler, nor a non-pure entry with a pure handler. When a handler needs side effects, the whole dispatcher is not pure; when the whole dispatcher should be pure, every handler is.

See Q560 for purity basics. See Q566 for pure method restrictions. See Q60 for dispatcher fundamentals. See Q620 for dispatcher hierarchy rules.

Example

defines module qa.dispatchervalidation.purity

  defines class

    Item as abstract
      name() as pure abstract
        <- rtn as String?
      default operator ?

    Book extends Item
      title <- "untitled"

      Book()
        -> title as String
        this.title: title

      override name() as pure
        <- rtn as String: title

      default operator ?

    Magazine extends Item
      issue <- "unknown"

      Magazine()
        -> issue as String
        this.issue: issue

      override name() as pure
        <- rtn as String: issue

      default operator ?

    //CORRECT: pure dispatcher with all pure handlers
    PureCatalog
      describe() as pure dispatcher
        -> item as Item
        <- rtn as String: item.name()

      describe() as pure
        -> item as Book
        <- rtn as String: "Book: " + item.name()

      describe() as pure
        -> item as Magazine
        <- rtn as String: "Magazine: " + item.name()

    //CORRECT: a dispatcher that is not pure, with no pure handler
    LoggingCatalog
      labelled as Integer: 0

      label() as dispatcher
        -> item as Item
        <- rtn as String?
        labelled++
        rtn: item.name()

      label()
        -> item as Book
        <- rtn as String: "Book: " + item.name()

      default operator ?

  defines function

    testPureDispatcher()
      catalog <- PureCatalog()
      book <- Book("EK9 Guide")
      mag <- Magazine("Tech Monthly")

      bookResult <- catalog.describe(book)
      magResult <- catalog.describe(mag)

      require bookResult == "Book: EK9 Guide"
      require magResult == "Magazine: Tech Monthly"

      logging <- LoggingCatalog()
      require logging.label(book) == "Book: EK9 Guide"
      require logging.label(mag) == "Tech Monthly"

Common mistakes

E05170 — The dispatcher entry describe is declared as pure. Every handler must also be pure. Omitting as pure on a handler creates a purity mismatch. See ek9 -h E05170 for details.

Incorrect:

describe()
        -> item as Book

Correct:

describe() as pure
        -> item as Book

E05170 — The dispatcher entry label is not pure, so no handler may be pure either. Marking one handler as pure creates a purity mismatch: callers only see the entry point, so the handler's promise is one they can never rely on. See ek9 -h E05170 for details.

Incorrect:

label() as pure
        -> item as Book

Correct:

label()
        -> item as Book

E05210 — Each handler parameter type must be in the hierarchy of the dispatcher entry parameter type Item. A handler for an unrelated type like String can never be reached. See ek9 -h E05210 for details.

Incorrect:

describe() as pure
        -> item as String
        <- rtn as String: item

Correct:

describe() as pure
        -> item as Magazine
        <- rtn as String: "Magazine: " + item.name()
Other ways to ask this
  • What is E05170 in EK9?
  • Must dispatcher handlers match purity of the entry method?
  • Can a pure dispatcher have non-pure handlers?
  • Can a non-pure dispatcher have pure handlers?

Coming from another language?

Java: no purity concept, no dispatch purity checking. Kotlin: no purity enforcement on dispatchers. Haskell: all functions pure by default, dispatch via type classes. Rust: trait methods can be effectful or pure, but no dispatch purity contract. EK9: E05170 requires every handler to match the purity of the dispatcher entry point - all pure, or none pure.

Keywords: dispatch, entry, side-effect, E05170, dispatcher, pure, match, ambiguity, handler, contract, purity, immutable, validate, both-directions