Why must generic functions provide an implementation?

← Generics · Ref: Q649

A generic function that is not abstract must provide an implementation (a bodiless one is E07110, which asks you to declare it 'abstract' instead). A generic function declared 'as abstract' may omit its body, but then every dynamic function built from it must supply one (E06100).

IMPLEMENTATION REQUIRED

A non-abstract generic function must have a body:

  defines function
    transform() of type T as open
      -> item as T
      <- rtn as T: T(item)

The body provides the default behavior.

AS OPEN FOR EXTENSION

Mark a generic function 'as open' to allow specialised implementations:

  defines function
    compare() of type T as open
      ->
        a as T
        b as T
      <- rtn as Integer: a <=> b

Concreted implementations can override this default.

DYNAMIC INSTANTIATION

Generic functions create concrete instances:

  intCompare <- () is compare of Integer as function

This uses the template's default implementation.

CUSTOM IMPLEMENTATION

Dynamic functions can provide custom bodies:

  customCompare <- () is compare of Integer as function
    rtn: b <=> a  // reversed

See Q197 for extending generics. See Q647 for function constraint rules. See Q194 for generic class basics.

Example

defines module qa.genericsdeep.functionimplementation

  defines function

    <?-
      Correct: generic function with implementation body.
      'as open' allows extension when parameterised.
    -?>
    doubleValue() of type T as open
      -> item as T
      <- rtn as T: item + item

    <?-
      Correct: multi-param generic function with body.
    -?>
    selectFirst() of type (A, B) as open
      ->
        first as A
        second as B
      <- rtn as A?
      require second?
      rtn: A(first)

    <?-
      Correct: constrained generic function with implementation.
    -?>
    stringify() of type T as open
      -> item as T
      <- rtn as String: $item

  defines program

    GenericFunctionImplDemo()
      stdout <- Stdout()

      // === USING DEFAULT IMPLEMENTATIONS ===

      doubled <- doubleValue(21)
      stdout.println(`Doubled: ${doubled}`)

      strDoubled <- doubleValue("ab")
      stdout.println(`String doubled: ${strDoubled}`)

      // === MULTI-PARAM GENERIC FUNCTION ===

      selected <- selectFirst("chosen", 99)
      stdout.println(`Selected: ${selected}`)

      // === STRINGIFY GENERIC ===

      asStr <- stringify(3.14)
      stdout.println(`Stringified: ${asStr}`)

Common mistakes

E07110 — A generic function declared 'as open' must provide an implementation body, because a template with a body is what gets instantiated for each type. A bodiless generic function must be declared 'abstract' instead (E07110), and then a dynamic function created from it must supply the body (E06100). See ek9 -h E07110 for details.

Incorrect:

doubleValue() of type T as open
      -> item as T
      <- rtn as T?

Correct:

doubleValue() of type T as open
      -> item as T
      <- rtn as T: item + item
Other ways to ask this
  • What is E06100 generic function implementation required?
  • Can I declare a generic function without a body?
  • How do generic function templates work in EK9?

Coming from another language?

Java: abstract methods exist but generic methods always have bodies. C++: templates always have bodies (no separate declaration for function templates). Rust: generic functions always have bodies. Go: generic functions always have bodies. Kotlin: generic functions always have bodies. EK9: a non-abstract generic function needs a body (E07110); an abstract generic function may omit it, and a dynamic function created from an abstract generic function must supply the body (E06100).

Keywords: required, type-parameter, implementation, open, function, body, template, generic, E06100