Why can't I use a function type to parameterize a generic that calls constructors?

← Generics · Ref: Q833

EK9 generics discover implicit requirements from their bodies. If a generic uses 'T()' (constructor) or 'arg0 <=> arg1' (comparator), those become requirements on the parameterizing type. Functions cannot satisfy constructor or comparator requirements, so parameterizing such generics with a function triggers E06110.

IMPLICIT CONTRACT DISCOVERY

When you write:

  Holder of type T
    createNew() <- rtn as T: T()      // Requires T has a constructor
    compare() <- rtn as Integer: a <=> b  // Requires T has <=>

The compiler discovers that T must have a constructor and a comparator.

FUNCTION LIMITATIONS

Functions cannot be constructed with 'T()' — they are not classes. Functions cannot be compared with '<=>'. So a function can only parameterize generics that use minimal capabilities like '?' (isSet).

CONTAINER-STYLE GENERICS WORK

A generic that only checks 'arg0?' and 'arg1?' works with functions — like List or Optional. These are container-style generics that just hold values.

THIS EXAMPLE

Shows a container-style generic (SafeHolder) that works with both classes and functions, alongside a concrete class (Counter) that satisfies constructor requirements.

See Q646 for type inference limits. See Q707 for Consumer/Acceptor. See Q58 for generic functions.

Example

defines module qa.generics.function.in.generic

  defines function

    <?-
      Abstract function type for use as a delegate/generic parameter.
    -?>
    CheckFn as abstract
      -> arg0 as Integer
      <- rtn as Boolean?

    IsPositive() is CheckFn
      -> arg0 as Integer
      <- rtn as Boolean: arg0 > 0

  defines class

    Counter
      count <- 0

      default Counter()

      Counter()
        -> initial as Integer
        count :=: initial

      increment()
        count += 1

      value()
        <- rtn as Integer: Integer(count)

      default operator ?

    <?-
      A container-style generic — only uses '?' operator.
      This works with BOTH classes AND functions.
    -?>
    SafeHolder of type T

      default SafeHolder()

      default SafeHolder()
        -> arg0 as T

      check()
        ->
          arg0 as T
          arg1 as T
        <-
          rtn as Boolean: arg0? and arg1?

    <?-
      Generic that uses T() constructor — NOT safe for functions.
    -?>
    ConstructingHolder of type T
      default ConstructingHolder()
      default ConstructingHolder()
        -> arg0 as T
      check()
        ->
          arg0 as T
          arg1 as T
        <-
          rtn as Boolean: arg0? and arg1?
      createNew()
        <- rtn as T: T()

    <?-
      Generic that uses <=> comparator — NOT safe for functions.
    -?>
    ComparingHolder of type T
      default ComparingHolder()
      default ComparingHolder()
        -> arg0 as T
      check()
        ->
          arg0 as T
          arg1 as T
        <-
          rtn as Boolean: arg0? and arg1?
      compare()
        ->
          arg0 as T
          arg1 as T
        <-
          rtn as Integer: arg0 <=> arg1

  defines program

    GenericFunctionDemo()
      stdout <- Stdout()

      //Functions work with container-style generics
      holder <- SafeHolder() of CheckFn
      result <- holder.check(IsPositive, IsPositive)
      stdout.println(`Function holder check: ${result}`)

      //Classes work with any generic
      counterHolder <- SafeHolder() of Counter
      classResult <- counterHolder.check(Counter(), Counter(5))
      stdout.println(`Counter holder check: ${classResult}`)

Common mistakes

E06110 — ConstructingHolder uses 'T()' in its body, requiring a constructor. Functions cannot be constructed. Use a container-style generic that only uses '?' (isSet) with functions. See ek9 -h E06110 for details.

Incorrect:

      holder <- ConstructingHolder() of CheckFn

Correct:

      holder <- SafeHolder() of CheckFn

E06120 — ComparingHolder uses 'arg0 <=> arg1' in its body. Functions cannot be compared. Use a generic that only checks isSet with functions. See ek9 -h E06120 for details.

Incorrect:

      holder <- ComparingHolder() of CheckFn

Correct:

      holder <- SafeHolder() of CheckFn
Other ways to ask this
  • What is E06110 CONSTRUCTOR_WITH_FUNCTION_IN_GENERIC?
  • Why does EK9 reject my generic when I parameterize it with a function?
  • What are the limitations of using functions with generic types?

Coming from another language?

Java: type erasure means generic constraint violations appear as ClassCastExceptions at runtime. C++: templates check at instantiation with poor diagnostics. Rust: requires explicit trait bounds ('T: Ord'). EK9: discovers implicit requirements from generic body and validates at parameterization site with specific error codes.

Keywords: container, comparator, constructor, isSet, constraint, E06110, generic, implicit, function, parameterize